It just sounds better.
+++ /dev/null
-# Makefile for C source code
-#
-# Copyright (C) 2011 Colin Walters <walters@verbum.org>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-#
-# Author: Colin Walters <walters@verbum.org>
-
-noinst_LTLIBRARIES += libhtutil.la
-
-libhtutil_la_SOURCES = \
- src/libhtutil/ht-unix-utils.c \
- src/libhtutil/ht-unix-utils.h \
- src/libhtutil/ht-gio-utils.c \
- src/libhtutil/ht-gio-utils.h \
- src/libhtutil/htutil.h \
- $(NULL)
-libhtutil_la_CFLAGS = -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-libhtutil_la_LIBADD = $(GIO_UNIX_LIBS)
-
-noinst_LTLIBRARIES += libhacktree.la
-
-libhacktree_la_SOURCES = src/libhacktree/hacktree.h \
- src/libhacktree/hacktree-core.c \
- src/libhacktree/hacktree-core.h \
- src/libhacktree/hacktree-repo.c \
- src/libhacktree/hacktree-repo.h \
- src/libhacktree/hacktree-types.h \
- $(NULL)
-libhacktree_la_CFLAGS = -I$(srcdir)/src/libhacktree -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-libhacktree_la_LIBADD = libhtutil.la $(GIO_UNIX_LIBS)
-
-bin_PROGRAMS += hacktree
-
-hacktree_SOURCES = src/main.c \
- src/ht-builtins.h \
- src/ht-builtin-checkout.c \
- src/ht-builtin-commit.c \
- src/ht-builtin-fsck.c \
- src/ht-builtin-init.c \
- src/ht-builtin-link-file.c \
- src/ht-builtin-log.c \
- src/ht-builtin-show.c \
- $(NULL)
-hacktree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libhacktree -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-hacktree_LDADD = libhtutil.la libhacktree.la $(GIO_UNIX_LIBS)
--- /dev/null
+# Makefile for C source code
+#
+# Copyright (C) 2011 Colin Walters <walters@verbum.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Author: Colin Walters <walters@verbum.org>
+
+noinst_LTLIBRARIES += libotutil.la
+
+libotutil_la_SOURCES = \
+ src/libotutil/ot-unix-utils.c \
+ src/libotutil/ot-unix-utils.h \
+ src/libotutil/ot-gio-utils.c \
+ src/libotutil/ot-gio-utils.h \
+ src/libotutil/otutil.h \
+ $(NULL)
+libotutil_la_CFLAGS = -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+libotutil_la_LIBADD = $(GIO_UNIX_LIBS)
+
+noinst_LTLIBRARIES += libostree.la
+
+libostree_la_SOURCES = src/libostree/ostree.h \
+ src/libostree/ostree-core.c \
+ src/libostree/ostree-core.h \
+ src/libostree/ostree-repo.c \
+ src/libostree/ostree-repo.h \
+ src/libostree/ostree-types.h \
+ $(NULL)
+libostree_la_CFLAGS = -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+libostree_la_LIBADD = libotutil.la $(GIO_UNIX_LIBS)
+
+bin_PROGRAMS += ostree
+
+ostree_SOURCES = src/main.c \
+ src/ot-builtins.h \
+ src/ot-builtin-checkout.c \
+ src/ot-builtin-commit.c \
+ src/ot-builtin-fsck.c \
+ src/ot-builtin-init.c \
+ src/ot-builtin-link-file.c \
+ src/ot-builtin-log.c \
+ src/ot-builtin-show.c \
+ $(NULL)
+ostree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+ostree_LDADD = libotutil.la libostree.la $(GIO_UNIX_LIBS)
noinst_LTLIBRARIES =
noinst_PROGRAMS =
-
-include Makefile-hacktree.am
+include Makefile-src.am
-Hacktree
+OSTree
========
Problem statement
the old package.
This is most realistic option for people hacking on system
- components currently, but hacktree will be better.
+ components currently, but ostree will be better.
- LXC / containers
this means you can't build NetworkManager, and thus are permanently
stuck on whatever the distro provides.
-Who is hacktree for?
+Who is ostree for?
------------------------------
First - operating system developers and testers. I specifically keep
Now, what we can do is have a system that installs chroots as a subdirectory
of the root, like:
- /usr
- /etc
- /home
- /gnomeos/root-3.0-opt/{usr,etc,var,...}
- /gnomeos/root-3.2-opt/{usr,etc,var,...}
+ /ostree/gnomeos-3.0-opt-393a4555/{usr,etc,sbin,...}
+ /ostree/gnomeos-3.2-opt-7e9788a2/{usr,etc,sbin,...}
These live in the same root filesystem as your regular distribution
(Note though, the root partition should be reasonably sized, or
hopefully you've used just one big partition).
-You should be able to boot into one of these roots. Since hacktree
+You should be able to boot into one of these roots. Since ostree
lives inside a distro created partition, a tricky part here is that we
need to know how to interact with the installed distribution's grub.
This is an annoying but tractable problem.
-Hacktree will allow efficiently parallel installing and downloading OS
+OSTree will allow efficiently parallel installing and downloading OS
builds.
An important note here is that we explicitly link /home in each root
we share uid/gid, so /etc/passwd will have to be in sync. Probably
what we'll do is have a script to pull the data from the "host" OS.
+Other shared directories are /var and /root. Note that /etc is
+explicitly NOT shared!
+
+On a pure OSTree system, the filesystem layout will look like this:
+
+ .
+ |-- boot
+ |-- home
+ |-- ostree
+ | |-- current -> gnomeos-3.2-opt-7e9788a2
+ | |-- gnomeos-3.0-opt-393a4555
+ | | |-- etc
+ | | |-- lib
+ | | |-- mnt
+ | | |-- proc
+ | | |-- run
+ | | |-- sbin
+ | | |-- srv
+ | | |-- sys
+ | | `-- usr
+ | `-- gnomeos-3.2-opt-7e9788a2
+ | |-- etc
+ | |-- lib
+ | |-- mnt
+ | |-- proc
+ | |-- run
+ | |-- sbin
+ | |-- srv
+ | |-- sys
+ | `-- usr
+ |-- root
+ `-- var
+
+
Making this efficient
---------------------
This is probably tractable over a long period of time, but if anything
has a bug, then it corrupts the file effectively.
2. Make the core OS read-only, with a well-defined mechanism for mutating
- under the control of hacktree.
+ under the control of ostree.
I chose 2.
A userspace content-addressed versioning filesystem
---------------------------------------------------
-At its very core, that's what hacktree is. Just like git. If you
+At its very core, that's what ostree is. Just like git. If you
understand git, you know it's not like other revision control systems.
git is effectively a specialized, userspace filesystem, and that is a
very powerful idea.
example, under the assumption that you have a lot of text. Its
handling of binaries is very generic and unoptimized.
-In contrast, hacktree is explicitly designed for binaries, and in
+In contrast, ostree is explicitly designed for binaries, and in
particular one type of binary - ELF executables (or it will be once we
start using bsdiff).
-Another big difference versus git is that hacktree uses hard links
+Another big difference versus git is that ostree uses hard links
between "checkouts" and the repository. This means each checkout uses
almost no additional space, and is *extremely* fast to check out. We
can do this because again each checkout is designed to be read-only.
-So we mentioned above the
+So we mentioned above there are:
/gnomeos/root-3.0-opt
/gnomeos/root-3.2-opt
Atomic upgrades, rollback
-------------------------
-Hacktree is designed to atomically swap operating systems - such that
+OSTree is designed to atomically swap operating systems - such that
during an upgrade and reboot process, you either get the full new
system, or the old one. There is no "Please don't turn off your
computer". We do this by simply using a symbolic link like:
Basically I think they're a broken idea. There are several different
classes of things that demand targeted solutions:
- 1. Managing and upgrading the core OS (hacktree)
+ 1. Managing and upgrading the core OS (ostree)
2. Managing and upgrading desktop applications (gnome-shell, glick?)
3. System extensions - these are arbitrary RPMs like say the nVidia driver.
We apply them after constructing each root. Media codecs also fall
into this category.
-How one might install say Apache on top of hacktree is an open
+How one might install say Apache on top of ostree is an open
question - I think it probably makes sense honestly to ship services
like this with no configuration - just the binaries. Then admins can
do whatever they want.
Downloads
---------
-I'm pretty sure hacktree should be significantly better than RPM with
+I'm pretty sure ostree should be significantly better than RPM with
deltarpms. Note we only download changed objects. If say just one
translation changes, we only download that new translation! One
problem we will have to hunt down is programs that inject
unpacking packages into the live system. This problem is really
important to me.
-Note though hacktree can definitely take advantage of BTRFS features!
+Note though ostree can definitely take advantage of BTRFS features!
In particular, we could use "reflink"
<http://lwn.net/Articles/331808/> instead of hard links, and avoid
having the object store corrupted if somehow the files are modified
- Conary: <http://wiki.rpath.com/wiki/Conary:Updates_and_Rollbacks>
If rpm/dpkg are like CVS, Conary is closer to Subversion. It's not
- bad, but hacktree is better than it for the exact same reasons git
+ bad, but ostree is better than it for the exact same reasons git
is better than Subversion.
- BTRFS: <http://en.wikipedia.org/wiki/Btrfs>
- Jhbuild: <https://live.gnome.org/Jhbuild>
What we've been using in GNOME, and has the essential property of allowing you
- to "fall back" to a stable system. But hacktree will blow it out of the water.
+ to "fall back" to a stable system. But ostree will blow it out of the water.
AC_PREREQ([2.63])
-AC_INIT([hacktree], [0], [walters@verbum.org])
+AC_INIT([ostree], [0], [walters@verbum.org])
AC_CONFIG_HEADER([config.h])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux])
xmlns:gnome="http://api.gnome.org/doap-extensions#"
xmlns="http://usefulinc.com/ns/doap#">
- <name>hacktree</name>
- <shortname>hacktree</shortname>
+ <name>ostree</name>
+ <shortname>ostree</shortname>
<shortdesc xml:lang="en">GNOME OS build tool</shortdesc>
$ mkdir r0
$ DIRS="bin dev etc lib lib32 lib64 media mnt opt proc root run sbin selinux srv sys tmp usr"
$ mv $DIRS r0
+ $ mkdir r0/{boot,var,home}
+ $ touch r0/{boot,var,home}/EMPTY
-Note that /boot, /home and /var are left shared. Now with it still
+Note that /boot, /home and /var are left shared; we create empty
+destination directories that will be mounted over. Now with it still
mounted, we need to move on to the next part - modifying the initrd.
Then I started hacking on the initrd, making understand how to chroot
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-
-static GOptionEntry options[] = {
- { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
- { NULL }
-};
-
-gboolean
-hacktree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error)
-{
- GOptionContext *context;
- gboolean ret = FALSE;
- HacktreeRepo *repo = NULL;
- int i;
- const char *commit;
- const char *destination;
-
- context = g_option_context_new ("COMMIT DESTINATION - Check out a commit into a filesystem tree");
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (repo_path == NULL)
- repo_path = ".";
-
- repo = hacktree_repo_new (repo_path);
- if (!hacktree_repo_check (repo, error))
- goto out;
-
- if (argc < 3)
- {
- gchar *help = g_option_context_get_help (context, TRUE, NULL);
- g_printerr ("%s\n", help);
- g_free (help);
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "COMMIT and DESTINATION must be specified");
- goto out;
- }
-
- commit = argv[1];
- destination = argv[2];
-
- if (!hacktree_repo_checkout (repo, commit, destination, error))
- goto out;
-
- ret = TRUE;
- out:
- if (context)
- g_option_context_free (context);
- g_clear_object (&repo);
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-static gboolean separator_null;
-static int from_fd = -1;
-static gboolean from_stdin;
-static char *from_file;
-static char *subject;
-static char *body;
-static char **additions;
-static char **removals;
-
-static GOptionEntry options[] = {
- { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
- { "subject", 's', 0, G_OPTION_ARG_STRING, &subject, "One line subject", "subject" },
- { "body", 'b', 0, G_OPTION_ARG_STRING, &body, "Full description", "body" },
- { "from-fd", 0, 0, G_OPTION_ARG_INT, &from_fd, "Read new tree files from fd", "file descriptor" },
- { "from-stdin", 0, 0, G_OPTION_ARG_NONE, &from_stdin, "Read new tree files from stdin", "file descriptor" },
- { "from-file", 0, 0, G_OPTION_ARG_FILENAME, &from_file, "Read new tree files from another file", "path" },
- { "separator-null", 0, 0, G_OPTION_ARG_NONE, &separator_null, "", "Use '\\0' as filename separator, as with find -print0" },
- { "add", 'a', 0, G_OPTION_ARG_FILENAME_ARRAY, &additions, "Relative file path to add", "filename" },
- { "remove", 'r', 0, G_OPTION_ARG_FILENAME_ARRAY, &removals, "Relative file path to remove", "filename" },
- { NULL }
-};
-
-gboolean
-hacktree_builtin_commit (int argc, char **argv, const char *prefix, GError **error)
-{
- GOptionContext *context;
- gboolean ret = FALSE;
- HacktreeRepo *repo = NULL;
- gboolean using_filename_cmdline;
- gboolean using_filedescriptors;
- GPtrArray *additions_array = NULL;
- GPtrArray *removals_array = NULL;
- GChecksum *commit_checksum = NULL;
- char **iter;
-
- context = g_option_context_new ("- Commit a new revision");
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (repo_path == NULL)
- repo_path = ".";
- if (prefix == NULL)
- prefix = ".";
-
- repo = hacktree_repo_new (repo_path);
- if (!hacktree_repo_check (repo, error))
- goto out;
-
- using_filename_cmdline = (removals || additions);
- using_filedescriptors = (from_file || from_fd >= 0 || from_stdin);
-
- if (!(using_filename_cmdline || using_filedescriptors))
- {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No additions or removals specified");
- goto out;
- }
- if (using_filename_cmdline && using_filedescriptors)
- {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "File descriptors may not be combined with --add or --remove");
- goto out;
- }
-
- if (!subject)
- {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "A subject must be specified with --subject");
- goto out;
- }
-
- if (using_filename_cmdline)
- {
- g_assert (removals || additions);
- additions_array = g_ptr_array_new ();
- removals_array = g_ptr_array_new ();
-
- if (additions)
- for (iter = additions; *iter; iter++)
- g_ptr_array_add (additions_array, *iter);
- if (removals)
- for (iter = removals; *iter; iter++)
- g_ptr_array_add (removals_array, *iter);
-
- if (!hacktree_repo_commit (repo, subject, body, NULL,
- prefix, additions_array,
- removals_array,
- &commit_checksum,
- error))
- goto out;
- }
- else if (using_filedescriptors)
- {
- char separator = separator_null ? '\0' : '\n';
- gboolean temp_fd = -1;
-
- if (from_stdin)
- from_fd = 0;
- else if (from_file)
- {
- temp_fd = ht_util_open_file_read (from_file, error);
- if (temp_fd < 0)
- {
- g_prefix_error (error, "Failed to open '%s': ", from_file);
- goto out;
- }
- from_fd = temp_fd;
- }
- if (!hacktree_repo_commit_from_filelist_fd (repo, subject, body, NULL,
- prefix, from_fd, separator,
- &commit_checksum, error))
- {
- if (temp_fd >= 0)
- close (temp_fd);
- goto out;
- }
- if (temp_fd >= 0)
- close (temp_fd);
- }
-
- ret = TRUE;
- g_print ("%s\n", g_checksum_get_string (commit_checksum));
- out:
- if (context)
- g_option_context_free (context);
- g_clear_object (&repo);
- if (removals_array)
- g_ptr_array_free (removals_array, TRUE);
- if (additions_array)
- g_ptr_array_free (additions_array, TRUE);
- if (commit_checksum)
- g_checksum_free (commit_checksum);
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-static gboolean quiet;
-
-static GOptionEntry options[] = {
- { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
- { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Don't display informational messages", NULL },
- { NULL }
-};
-
-typedef struct {
- guint n_objects;
-} HtFsckData;
-
-static void
-object_iter_callback (HacktreeRepo *repo,
- const char *path,
- GFileInfo *file_info,
- gpointer user_data)
-{
- HtFsckData *data = user_data;
- struct stat stbuf;
- GChecksum *checksum = NULL;
- GError *error = NULL;
- char *dirname = NULL;
- char *checksum_prefix = NULL;
- char *checksum_string = NULL;
- char *filename_checksum = NULL;
- char *dot;
-
- dirname = g_path_get_dirname (path);
- checksum_prefix = g_path_get_basename (dirname);
-
- /* nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink");
- if (nlinks < 2 && !quiet)
- g_printerr ("note: floating object: %s\n", path); */
-
- if (!hacktree_stat_and_checksum_file (-1, path, &checksum, &stbuf, &error))
- goto out;
-
- filename_checksum = g_strdup (g_file_info_get_name (file_info));
- dot = strrchr (filename_checksum, '.');
- g_assert (dot != NULL);
- *dot = '\0';
-
- checksum_string = g_strconcat (checksum_prefix, filename_checksum, NULL);
-
- if (strcmp (checksum_string, g_checksum_get_string (checksum)) != 0)
- {
- g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n",
- path, g_checksum_get_string (checksum));
- }
-
- data->n_objects++;
-
- out:
- if (checksum != NULL)
- g_checksum_free (checksum);
- g_free (dirname);
- g_free (checksum_prefix);
- g_free (checksum_string);
- g_free (filename_checksum);
- if (error != NULL)
- {
- g_printerr ("%s\n", error->message);
- g_clear_error (&error);
- }
-}
-
-gboolean
-hacktree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error)
-{
- GOptionContext *context;
- HtFsckData data;
- gboolean ret = FALSE;
- HacktreeRepo *repo = NULL;
- const char *head;
-
- context = g_option_context_new ("- Check the repository for consistency");
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (repo_path == NULL)
- repo_path = ".";
-
- data.n_objects = 0;
-
- repo = hacktree_repo_new (repo_path);
- if (!hacktree_repo_check (repo, error))
- goto out;
-
- if (!hacktree_repo_iter_objects (repo, object_iter_callback, &data, error))
- goto out;
-
- head = hacktree_repo_get_head (repo);
- if (!head)
- {
- if (!quiet)
- g_printerr ("No HEAD file\n");
- }
-
- if (!quiet)
- g_printerr ("Total Objects: %u\n", data.n_objects);
-
- ret = TRUE;
- out:
- if (context)
- g_option_context_free (context);
- g_clear_object (&repo);
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-static GOptionEntry options[] = {
- { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
- { NULL }
-};
-
-gboolean
-hacktree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
-{
- GOptionContext *context = NULL;
- gboolean ret = FALSE;
- char *htdir_path = NULL;
- char *objects_path = NULL;
- GFile *htdir = NULL;
- GFile *objects_dir = NULL;
-
- context = g_option_context_new ("- Check the repository for consistency");
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (repo_path == NULL)
- repo_path = ".";
-
- htdir_path = g_build_filename (repo_path, HACKTREE_REPO_DIR, NULL);
- htdir = ht_util_new_file_for_path (htdir_path);
-
- if (!g_file_make_directory (htdir, NULL, error))
- goto out;
-
- objects_path = g_build_filename (htdir_path, "objects", NULL);
- objects_dir = g_file_new_for_path (objects_path);
- if (!g_file_make_directory (objects_dir, NULL, error))
- goto out;
-
- ret = TRUE;
- out:
- if (context)
- g_option_context_free (context);
- g_free (htdir_path);
- g_clear_object (&htdir);
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-static gboolean ignore_exists;
-static gboolean force;
-
-static GOptionEntry options[] = {
- { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
- { "ignore-exists", 'n', 0, G_OPTION_ARG_NONE, &ignore_exists, "Don't error if file exists", NULL },
- { "force", 'f', 0, G_OPTION_ARG_NONE, &force, "If object exists, relink file", NULL },
- { NULL }
-};
-
-gboolean
-hacktree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error)
-{
- GOptionContext *context;
- gboolean ret = FALSE;
- HacktreeRepo *repo = NULL;
- int i;
-
- context = g_option_context_new ("- Create a new hard link in the repository");
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (repo_path == NULL)
- repo_path = ".";
-
- repo = hacktree_repo_new (repo_path);
- if (!hacktree_repo_check (repo, error))
- goto out;
-
- for (i = 0; i < argc-1; i++)
- {
- if (!hacktree_repo_link_file (repo, argv[i+1], ignore_exists, force, error))
- goto out;
- }
-
- ret = TRUE;
- out:
- if (context)
- g_option_context_free (context);
- g_clear_object (&repo);
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-
-static GOptionEntry options[] = {
- { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
- { NULL }
-};
-
-gboolean
-hacktree_builtin_log (int argc, char **argv, const char *prefix, GError **error)
-{
- GOptionContext *context;
- gboolean ret = FALSE;
- HacktreeRepo *repo = NULL;
- GOutputStream *pager = NULL;
- GVariant *commit = NULL;
- char *head;
-
- context = g_option_context_new ("- Show revision log");
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (repo_path == NULL)
- repo_path = ".";
- if (prefix == NULL)
- prefix = ".";
-
- repo = hacktree_repo_new (repo_path);
- if (!hacktree_repo_check (repo, error))
- goto out;
-
- head = g_strdup (hacktree_repo_get_head (repo));
- if (!head)
- {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No HEAD exists");
- goto out;
- }
-
- if (!ht_util_spawn_pager (&pager, error))
- goto out;
-
- while (TRUE)
- {
- HacktreeSerializedVariantType type;
- char *formatted = NULL;
- guint32 version;
- const char *parent;
- const char *subject;
- const char *body;
- guint64 timestamp;
- const char *contents;
- const char *root_metadata;
- GDateTime *time_obj = NULL;
- char *formatted_date = NULL;
- const char *body_newline;
- gsize bytes_written;
- GVariant *commit_metadata = NULL;
- char *formatted_metadata = NULL;
-
- if (commit)
- g_variant_unref (commit);
- if (!hacktree_repo_load_variant (repo, head, &type, &commit, error))
- goto out;
-
- /* Ignore commit metadata for now */
- g_variant_get (commit, "(u@a{sv}&s&s&st&s&s)",
- &version, &commit_metadata, &parent, &subject, &body,
- ×tamp, &contents, &root_metadata);
- time_obj = g_date_time_new_from_unix_utc (timestamp);
- formatted_date = g_date_time_format (time_obj, "%a %b %d %H:%M:%S %Y %z");
- g_date_time_unref (time_obj);
- time_obj = NULL;
-
- formatted_metadata = g_variant_print (commit_metadata, TRUE);
- g_variant_unref (commit_metadata);
- formatted = g_strdup_printf ("commit %s\nSubject: %s\nDate: %s\nMetadata: %s\n\n",
- head, subject, formatted_date, formatted_metadata);
- g_free (formatted_metadata);
- g_free (formatted_date);
- formatted_date = NULL;
-
- if (!g_output_stream_write_all (pager, formatted, strlen (formatted), &bytes_written, NULL, error))
- {
- g_free (formatted);
- goto out;
- }
- g_free (formatted);
-
- body_newline = strchr (body, '\n');
- do {
- gsize len;
- if (!g_output_stream_write_all (pager, " ", 4, &bytes_written, NULL, error))
- goto out;
- len = body_newline ? body_newline - body : strlen (body);
- if (!g_output_stream_write_all (pager, body, len, &bytes_written, NULL, error))
- goto out;
- if (!g_output_stream_write_all (pager, "\n\n", 2, &bytes_written, NULL, error))
- goto out;
- body_newline = strchr (body, '\n');
- if (!body_newline)
- break;
- else
- body_newline += 1;
- } while (*body_newline);
-
- if (strcmp (parent, "") == 0)
- break;
- g_free (head);
- head = g_strdup (parent);
- }
-
- if (!g_output_stream_close (pager, NULL, error))
- goto out;
-
- ret = TRUE;
- out:
- if (context)
- g_option_context_free (context);
- if (commit)
- g_variant_unref (commit);
- g_clear_object (&repo);
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-
-static GOptionEntry options[] = {
- { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
- { NULL }
-};
-
-gboolean
-hacktree_builtin_show (int argc, char **argv, const char *prefix, GError **error)
-{
- GOptionContext *context;
- gboolean ret = FALSE;
- HacktreeRepo *repo = NULL;
- int i;
- const char *target;
- HacktreeSerializedVariantType type;
- GVariant *variant = NULL;
- char *formatted_variant = NULL;
-
- context = g_option_context_new ("- Output a metadata object");
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (repo_path == NULL)
- repo_path = ".";
-
- repo = hacktree_repo_new (repo_path);
- if (!hacktree_repo_check (repo, error))
- goto out;
-
- if (argc < 2)
- {
- target = hacktree_repo_get_head (repo);
- if (!target)
- {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No arguments specified and no HEAD exists");
- goto out;
- }
- }
- else
- target = argv[1];
-
- if (!hacktree_repo_load_variant (repo, target, &type, &variant, error))
- goto out;
-
- g_print ("Object: %s\nType: %d\n", target, type);
- formatted_variant = g_variant_print (variant, TRUE);
- g_print ("%s\n", formatted_variant);
-
- ret = TRUE;
- out:
- if (context)
- g_option_context_free (context);
- g_clear_object (&repo);
- if (variant)
- g_variant_unref (variant);
- g_free (formatted_variant);
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_BUILTINS__
-#define __HACKTREE_BUILTINS__
-
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-typedef enum {
- HACKTREE_BUILTIN_FLAG_NONE = 0,
-} HacktreeBuiltinFlags;
-
-typedef struct {
- const char *name;
- gboolean (*fn) (int argc, char **argv, const char *prefix, GError **error);
- int flags; /* HacktreeBuiltinFlags */
-} HacktreeBuiltin;
-
-gboolean hacktree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_commit (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_init (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_log (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_show (int argc, char **argv, const char *prefix, GError **error);
-
-G_END_DECLS
-
-#endif
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "hacktree.h"
-#include "htutil.h"
-
-#include <sys/types.h>
-#include <attr/xattr.h>
-
-static char *
-stat_to_string (struct stat *stbuf)
-{
- return g_strdup_printf ("%u:%u:%u",
- (guint32)(stbuf->st_mode & ~S_IFMT),
- (guint32)stbuf->st_uid,
- (guint32)stbuf->st_gid);
-}
-
-static char *
-canonicalize_xattrs (char *xattr_string, size_t len)
-{
- char *p;
- GSList *xattrs = NULL;
- GSList *iter;
- GString *result;
-
- result = g_string_new (0);
-
- p = xattr_string;
- while (p < xattr_string+len)
- {
- xattrs = g_slist_prepend (xattrs, p);
- p += strlen (p) + 1;
- }
-
- xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
- for (iter = xattrs; iter; iter = iter->next)
- g_string_append (result, iter->data);
-
- g_slist_free (xattrs);
- return g_string_free (result, FALSE);
-}
-
-static gboolean
-read_xattr_name_array (const char *path,
- const char *xattrs,
- size_t len,
- GVariantBuilder *builder,
- GError **error)
-{
- gboolean ret = FALSE;
- const char *p;
-
- p = xattrs;
- while (p < xattrs+len)
- {
- ssize_t bytes_read;
- char *buf;
-
- bytes_read = lgetxattr (path, p, NULL, 0);
- if (bytes_read < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- if (bytes_read == 0)
- continue;
-
- buf = g_malloc (bytes_read);
- if (lgetxattr (path, p, buf, bytes_read) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- g_free (buf);
- goto out;
- }
-
- g_variant_builder_add (builder, "(@ay@ay)",
- g_variant_new_bytestring (p),
- g_variant_new_fixed_array (G_VARIANT_TYPE ("y"), buf, bytes_read, 1));
-
- g_free (buf);
- p = p + strlen (p) + 1;
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-GVariant *
-hacktree_get_xattrs_for_path (const char *path,
- GError **error)
-{
- GVariant *ret = NULL;
- GVariantBuilder builder;
- char *xattr_names = NULL;
- char *xattr_names_canonical = NULL;
- ssize_t bytes_read;
-
- g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
-
- bytes_read = llistxattr (path, NULL, 0);
-
- if (bytes_read < 0)
- {
- if (errno != ENOTSUP)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- }
- else if (bytes_read > 0)
- {
- const char *p;
- xattr_names = g_malloc (bytes_read);
- if (llistxattr (path, xattr_names, bytes_read) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read);
-
- if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error))
- goto out;
- }
-
- ret = g_variant_builder_end (&builder);
- out:
- if (!ret)
- g_variant_builder_clear (&builder);
- g_free (xattr_names);
- g_free (xattr_names_canonical);
- return ret;
-}
-
-gboolean
-hacktree_stat_and_checksum_file (int dir_fd, const char *path,
- GChecksum **out_checksum,
- struct stat *out_stbuf,
- GError **error)
-{
- GChecksum *content_sha256 = NULL;
- GChecksum *content_and_meta_sha256 = NULL;
- char *stat_string = NULL;
- ssize_t bytes_read;
- GVariant *xattrs = NULL;
- int fd = -1;
- DIR *temp_dir = NULL;
- char *basename = NULL;
- gboolean ret = FALSE;
- char *symlink_target = NULL;
- char *device_id = NULL;
- struct stat stbuf;
-
- basename = g_path_get_basename (path);
-
- if (dir_fd == -1)
- {
- char *dirname = g_path_get_dirname (path);
- temp_dir = opendir (dirname);
- if (temp_dir == NULL)
- {
- ht_util_set_error_from_errno (error, errno);
- g_free (dirname);
- }
- g_free (dirname);
- dir_fd = dirfd (temp_dir);
- }
-
- if (fstatat (dir_fd, basename, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- if (!S_ISLNK(stbuf.st_mode))
- {
- fd = ht_util_open_file_read_at (dir_fd, basename, error);
- if (fd < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- }
-
- stat_string = stat_to_string (&stbuf);
- xattrs = hacktree_get_xattrs_for_path (path, error);
- if (!xattrs)
- goto out;
-
- content_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
-
- if (S_ISREG(stbuf.st_mode))
- {
- guint8 buf[8192];
-
- while ((bytes_read = read (fd, buf, sizeof (buf))) > 0)
- g_checksum_update (content_sha256, buf, bytes_read);
- if (bytes_read < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- }
- else if (S_ISLNK(stbuf.st_mode))
- {
- symlink_target = g_malloc (PATH_MAX);
-
- if (readlinkat (dir_fd, basename, symlink_target, PATH_MAX) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- g_checksum_update (content_sha256, (guint8*)symlink_target, strlen (symlink_target));
- }
- else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode))
- {
- device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev);
- g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id));
- }
- else
- {
- g_set_error (error, G_IO_ERROR,
- G_IO_ERROR_FAILED,
- "Unsupported file '%s' (must be regular, symbolic link, or device)",
- path);
- goto out;
- }
-
- content_and_meta_sha256 = g_checksum_copy (content_sha256);
-
- g_checksum_update (content_and_meta_sha256, (guint8*)stat_string, strlen (stat_string));
- g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
-
- *out_stbuf = stbuf;
- *out_checksum = content_and_meta_sha256;
- ret = TRUE;
- out:
- if (fd >= 0)
- close (fd);
- if (temp_dir != NULL)
- closedir (temp_dir);
- g_free (symlink_target);
- g_free (basename);
- g_free (stat_string);
- if (xattrs)
- g_variant_unref (xattrs);
- if (content_sha256)
- g_checksum_free (content_sha256);
-
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef _HACKTREE_CORE
-#define _HACKTREE_CORE
-
-#include <htutil.h>
-
-G_BEGIN_DECLS
-
-#define HACKTREE_EMPTY_STRING_SHA256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
-
-typedef enum {
- HACKTREE_OBJECT_TYPE_FILE = 1,
- HACKTREE_OBJECT_TYPE_META = 2,
-} HacktreeObjectType;
-
-typedef enum {
- HACKTREE_SERIALIZED_TREE_VARIANT = 1,
- HACKTREE_SERIALIZED_COMMIT_VARIANT = 2,
- HACKTREE_SERIALIZED_DIRMETA_VARIANT = 3,
- HACKTREE_SERIALIZED_XATTR_VARIANT = 4
-} HacktreeSerializedVariantType;
-
-#define HACKTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
-
-/*
- * xattr objects:
- * a(ayay) - array of (name, value) pairs, both binary data, though name is a bytestring
- */
-#define HACKTREE_XATTR_GVARIANT_FORMAT "a(ayay)"
-
-#define HACKTREE_DIR_META_VERSION 0
-/*
- * dirmeta objects:
- * u - Version
- * u - uid
- * u - gid
- * u - mode
- * a(ayay) - xattrs
- */
-#define HACKTREE_DIRMETA_GVARIANT_FORMAT "(uuuua(ayay))"
-
-#define HACKTREE_TREE_VERSION 0
-/*
- * Tree objects:
- * u - Version
- * a{sv} - Metadata
- * a(ss) - array of (filename, checksum) for files
- * a(sss) - array of (dirname, tree_checksum, meta_checksum) for directories
- */
-#define HACKTREE_TREE_GVARIANT_FORMAT "(ua{sv}a(ss)a(sss)"
-
-#define HACKTREE_COMMIT_VERSION 0
-/*
- * Commit objects:
- * u - Version
- * a{sv} - Metadata
- * s - parent checksum (empty string for initial)
- * s - subject
- * s - body
- * t - Timestamp in seconds since the epoch (UTC)
- * s - Root tree contents
- * s - Root tree metadata
- */
-#define HACKTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)"
-
-GVariant *hacktree_get_xattrs_for_path (const char *path,
- GError **error);
-
-gboolean hacktree_stat_and_checksum_file (int dirfd, const char *path,
- GChecksum **out_checksum,
- struct stat *out_stbuf,
- GError **error);
-
-
-#endif /* _HACKTREE_REPO */
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "hacktree.h"
-#include "htutil.h"
-
-#include <gio/gunixoutputstream.h>
-#include <gio/gunixinputstream.h>
-
-static gboolean
-link_one_file (HacktreeRepo *self, const char *path,
- HacktreeObjectType type,
- gboolean ignore_exists, gboolean force,
- GChecksum **out_checksum,
- GError **error);
-static char *
-get_object_path (HacktreeRepo *self,
- const char *checksum,
- HacktreeObjectType type);
-
-enum {
- PROP_0,
-
- PROP_PATH
-};
-
-G_DEFINE_TYPE (HacktreeRepo, hacktree_repo, G_TYPE_OBJECT)
-
-#define GET_PRIVATE(o) \
- (G_TYPE_INSTANCE_GET_PRIVATE ((o), HACKTREE_TYPE_REPO, HacktreeRepoPrivate))
-
-typedef struct _HacktreeRepoPrivate HacktreeRepoPrivate;
-
-struct _HacktreeRepoPrivate {
- char *path;
- GFile *repo_file;
- char *head_ref_path;
- char *objects_path;
-
- gboolean inited;
- char *current_head;
-};
-
-static void
-hacktree_repo_finalize (GObject *object)
-{
- HacktreeRepo *self = HACKTREE_REPO (object);
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
- g_free (priv->path);
- g_clear_object (&priv->repo_file);
- g_free (priv->head_ref_path);
- g_free (priv->objects_path);
- g_free (priv->current_head);
-
- G_OBJECT_CLASS (hacktree_repo_parent_class)->finalize (object);
-}
-
-static void
-hacktree_repo_set_property(GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- HacktreeRepo *self = HACKTREE_REPO (object);
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
- switch (prop_id)
- {
- case PROP_PATH:
- priv->path = g_value_dup_string (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-hacktree_repo_get_property(GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- HacktreeRepo *self = HACKTREE_REPO (object);
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
- switch (prop_id)
- {
- case PROP_PATH:
- g_value_set_string (value, priv->path);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static GObject *
-hacktree_repo_constructor (GType gtype,
- guint n_properties,
- GObjectConstructParam *properties)
-{
- GObject *object;
- GObjectClass *parent_class;
- HacktreeRepoPrivate *priv;
-
- parent_class = G_OBJECT_CLASS (hacktree_repo_parent_class);
- object = parent_class->constructor (gtype, n_properties, properties);
-
- priv = GET_PRIVATE (object);
-
- g_assert (priv->path != NULL);
-
- priv->repo_file = ht_util_new_file_for_path (priv->path);
- priv->head_ref_path = g_build_filename (priv->path, HACKTREE_REPO_DIR, "HEAD", NULL);
- priv->objects_path = g_build_filename (priv->path, HACKTREE_REPO_DIR, "objects", NULL);
-
- return object;
-}
-
-static void
-hacktree_repo_class_init (HacktreeRepoClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (HacktreeRepoPrivate));
-
- object_class->constructor = hacktree_repo_constructor;
- object_class->get_property = hacktree_repo_get_property;
- object_class->set_property = hacktree_repo_set_property;
- object_class->finalize = hacktree_repo_finalize;
-
- g_object_class_install_property (object_class,
- PROP_PATH,
- g_param_spec_string ("path",
- "",
- "",
- NULL,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-}
-
-static void
-hacktree_repo_init (HacktreeRepo *self)
-{
-}
-
-HacktreeRepo*
-hacktree_repo_new (const char *path)
-{
- return g_object_new (HACKTREE_TYPE_REPO, "path", path, NULL);
-}
-
-static gboolean
-parse_checksum_file (HacktreeRepo *self,
- const char *path,
- char **sha256,
- GError **error)
-{
- GError *temp_error = NULL;
- gboolean ret = FALSE;
- char *ret_sha256 = NULL;
-
- ret_sha256 = ht_util_get_file_contents_utf8 (path, &temp_error);
- if (ret_sha256 == NULL)
- {
- if (g_error_matches (temp_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
- {
- g_clear_error (&temp_error);
- }
- else
- {
- g_propagate_error (error, temp_error);
- goto out;
- }
- }
- else
- {
- g_strchomp (ret_sha256);
- }
-
- *sha256 = ret_sha256;
- ret = TRUE;
- out:
- return ret;
-}
-
-static gboolean
-write_checksum_file (const char *path,
- const char *sha256,
- GError **error)
-{
- gboolean ret = FALSE;
- char *buf = NULL;
-
- buf = g_strconcat (sha256, "\n", NULL);
-
- if (!g_file_set_contents (path, buf, -1, error))
- goto out;
-
- ret = TRUE;
- out:
- g_free (buf);
- return ret;
-}
-
-gboolean
-hacktree_repo_check (HacktreeRepo *self, GError **error)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
- if (priv->inited)
- return TRUE;
-
- if (!g_file_test (priv->objects_path, G_FILE_TEST_IS_DIR))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Couldn't find objects directory '%s'", priv->objects_path);
- return FALSE;
- }
-
- priv->inited = TRUE;
-
- return parse_checksum_file (self, priv->head_ref_path, &priv->current_head, error);
-}
-
-static gboolean
-import_gvariant_object (HacktreeRepo *self,
- HacktreeSerializedVariantType type,
- GVariant *variant,
- GChecksum **out_checksum,
- GError **error)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- GVariant *serialized = NULL;
- gboolean ret = FALSE;
- gsize bytes_written;
- char *tmp_name = NULL;
- int fd = -1;
- GUnixOutputStream *stream = NULL;
-
- serialized = g_variant_new ("(uv)", (guint32)type, variant);
-
- tmp_name = g_build_filename (priv->objects_path, "variant-tmp-XXXXXX", NULL);
- fd = mkstemp (tmp_name);
- if (fd < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- stream = (GUnixOutputStream*)g_unix_output_stream_new (fd, FALSE);
- if (!g_output_stream_write_all ((GOutputStream*)stream,
- g_variant_get_data (serialized),
- g_variant_get_size (serialized),
- &bytes_written,
- NULL,
- error))
- goto out;
- if (!g_output_stream_close ((GOutputStream*)stream,
- NULL, error))
- goto out;
-
- if (!link_one_file (self, tmp_name, HACKTREE_OBJECT_TYPE_META,
- TRUE, FALSE, out_checksum, error))
- goto out;
-
- ret = TRUE;
- out:
- /* Unconditionally unlink; if we suceeded, there's a new link, if not, clean up. */
- (void) unlink (tmp_name);
- if (fd != -1)
- close (fd);
- if (serialized != NULL)
- g_variant_unref (serialized);
- g_free (tmp_name);
- g_clear_object (&stream);
- return ret;
-}
-
-static gboolean
-load_gvariant_object_unknown (HacktreeRepo *self,
- const char *sha256,
- HacktreeSerializedVariantType *out_type,
- GVariant **out_variant,
- GError **error)
-{
- GMappedFile *mfile = NULL;
- gboolean ret = FALSE;
- GVariant *ret_variant = NULL;
- GVariant *container = NULL;
- char *path = NULL;
- guint32 ret_type;
-
- path = get_object_path (self, sha256, HACKTREE_OBJECT_TYPE_META);
-
- mfile = g_mapped_file_new (path, FALSE, error);
- if (mfile == NULL)
- goto out;
- else
- {
- container = g_variant_new_from_data (G_VARIANT_TYPE (HACKTREE_SERIALIZED_VARIANT_FORMAT),
- g_mapped_file_get_contents (mfile),
- g_mapped_file_get_length (mfile),
- FALSE,
- (GDestroyNotify) g_mapped_file_unref,
- mfile);
- if (!g_variant_is_of_type (container, G_VARIANT_TYPE (HACKTREE_SERIALIZED_VARIANT_FORMAT)))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Corrupted metadata object '%s'", sha256);
- goto out;
- }
- g_variant_get (container, "(uv)",
- &ret_type, &ret_variant);
- mfile = NULL;
- }
-
- ret = TRUE;
- out:
- if (!ret)
- {
- if (ret_variant)
- g_variant_unref (ret_variant);
- }
- else
- {
- *out_type = ret_type;
- *out_variant = ret_variant;
- }
- if (container != NULL)
- g_variant_unref (container);
- g_free (path);
- if (mfile != NULL)
- g_mapped_file_unref (mfile);
- return ret;
-}
-
-static gboolean
-load_gvariant_object (HacktreeRepo *self,
- HacktreeSerializedVariantType expected_type,
- const char *sha256,
- GVariant **out_variant,
- GError **error)
-{
- gboolean ret = FALSE;
- HacktreeSerializedVariantType type;
- GVariant *ret_variant = NULL;
-
- if (!load_gvariant_object_unknown (self, sha256, &type, &ret_variant, error))
- goto out;
-
- if (type != expected_type)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Corrupted metadata object '%s'; found type %u, expected %u", sha256,
- type, (guint32)expected_type);
- goto out;
-
- }
-
- ret = TRUE;
- *out_variant = ret_variant;
- out:
- if (!ret)
- {
- if (ret_variant)
- g_variant_unref (ret_variant);
- }
- return ret;
-}
-
-static gboolean
-import_directory_meta (HacktreeRepo *self,
- const char *path,
- GVariant **out_variant,
- GChecksum **out_checksum,
- GError **error)
-{
- gboolean ret = FALSE;
- struct stat stbuf;
- GChecksum *ret_checksum = NULL;
- GVariant *dirmeta = NULL;
- GVariant *xattrs = NULL;
- gsize xattr_len;
-
- if (lstat (path, &stbuf) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- if (!S_ISDIR(stbuf.st_mode))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Not a directory: '%s'", path);
- goto out;
- }
-
- xattrs = hacktree_get_xattrs_for_path (path, error);
- if (!xattrs)
- goto out;
-
- dirmeta = g_variant_new ("(uuuu@a(ayay))",
- HACKTREE_DIR_META_VERSION,
- (guint32)stbuf.st_uid,
- (guint32)stbuf.st_gid,
- (guint32)(stbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)),
- xattrs);
- xattrs = NULL; /* was floating */
- g_variant_ref_sink (dirmeta);
-
- if (!import_gvariant_object (self, HACKTREE_SERIALIZED_DIRMETA_VARIANT,
- dirmeta, &ret_checksum, error))
- goto out;
-
- ret = TRUE;
- out:
- if (!ret)
- {
- if (ret_checksum)
- g_checksum_free (ret_checksum);
- if (dirmeta != NULL)
- g_variant_unref (dirmeta);
- }
- else
- {
- *out_checksum = ret_checksum;
- *out_variant = dirmeta;
- }
- if (xattrs)
- g_variant_unref (xattrs);
- return ret;
-}
-
-static char *
-get_object_path (HacktreeRepo *self,
- const char *checksum,
- HacktreeObjectType type)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- char *checksum_prefix;
- char *base_path;
- char *ret;
- const char *type_string;
-
- checksum_prefix = g_strndup (checksum, 2);
- base_path = g_build_filename (priv->objects_path, checksum_prefix, checksum + 2, NULL);
- switch (type)
- {
- case HACKTREE_OBJECT_TYPE_FILE:
- type_string = ".file";
- break;
- case HACKTREE_OBJECT_TYPE_META:
- type_string = ".meta";
- break;
- default:
- g_assert_not_reached ();
- }
- ret = g_strconcat (base_path, type_string, NULL);
- g_free (base_path);
- g_free (checksum_prefix);
-
- return ret;
-}
-
-static char *
-prepare_dir_for_checksum_get_object_path (HacktreeRepo *self,
- GChecksum *checksum,
- HacktreeObjectType type,
- GError **error)
-{
- char *checksum_dir = NULL;
- char *object_path = NULL;
-
- object_path = get_object_path (self, g_checksum_get_string (checksum), type);
- checksum_dir = g_path_get_dirname (object_path);
-
- if (!ht_util_ensure_directory (checksum_dir, FALSE, error))
- goto out;
-
- out:
- g_free (checksum_dir);
- return object_path;
-}
-
-static gboolean
-link_one_file (HacktreeRepo *self, const char *path, HacktreeObjectType type,
- gboolean ignore_exists, gboolean force,
- GChecksum **out_checksum,
- GError **error)
-{
- char *src_basename = NULL;
- char *src_dirname = NULL;
- char *dest_basename = NULL;
- char *tmp_dest_basename = NULL;
- char *dest_dirname = NULL;
- GChecksum *id = NULL;
- DIR *src_dir = NULL;
- DIR *dest_dir = NULL;
- gboolean ret = FALSE;
- struct stat stbuf;
- char *dest_path = NULL;
-
- src_basename = g_path_get_basename (path);
- src_dirname = g_path_get_dirname (path);
-
- src_dir = opendir (src_dirname);
- if (src_dir == NULL)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- if (!hacktree_stat_and_checksum_file (dirfd (src_dir), path, &id, &stbuf, error))
- goto out;
- dest_path = prepare_dir_for_checksum_get_object_path (self, id, type, error);
- if (!dest_path)
- goto out;
-
- dest_basename = g_path_get_basename (dest_path);
- dest_dirname = g_path_get_dirname (dest_path);
- dest_dir = opendir (dest_dirname);
- if (dest_dir == NULL)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- if (force)
- {
- tmp_dest_basename = g_strconcat (dest_basename, ".tmp", NULL);
- (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
- }
- else
- tmp_dest_basename = g_strdup (dest_basename);
-
- if (linkat (dirfd (src_dir), src_basename, dirfd (dest_dir), tmp_dest_basename, 0) < 0)
- {
- if (errno != EEXIST || !ignore_exists)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- }
-
- if (force)
- {
- if (renameat (dirfd (dest_dir), tmp_dest_basename,
- dirfd (dest_dir), dest_basename) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
- }
-
- *out_checksum = id;
- id = NULL;
- ret = TRUE;
- out:
- if (id != NULL)
- g_checksum_free (id);
- if (src_dir != NULL)
- closedir (src_dir);
- if (dest_dir != NULL)
- closedir (dest_dir);
- g_free (src_basename);
- g_free (src_dirname);
- g_free (dest_basename);
- g_free (tmp_dest_basename);
- g_free (dest_dirname);
- return ret;
-}
-
-gboolean
-hacktree_repo_link_file (HacktreeRepo *self,
- const char *path,
- gboolean ignore_exists,
- gboolean force,
- GError **error)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- GChecksum *checksum = NULL;
-
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
- g_return_val_if_fail (priv->inited, FALSE);
-
- if (!link_one_file (self, path, HACKTREE_OBJECT_TYPE_FILE,
- ignore_exists, force, &checksum, error))
- return FALSE;
- g_checksum_free (checksum);
- return TRUE;
-}
-
-typedef struct _ParsedTreeData ParsedTreeData;
-typedef struct _ParsedDirectoryData ParsedDirectoryData;
-
-static void parsed_tree_data_free (ParsedTreeData *pdata);
-
-struct _ParsedDirectoryData {
- ParsedTreeData *tree_data;
- char *metadata_sha256;
- GVariant *meta_data;
-};
-
-static void
-parsed_directory_data_free (ParsedDirectoryData *pdata)
-{
- if (pdata == NULL)
- return;
- parsed_tree_data_free (pdata->tree_data);
- g_free (pdata->metadata_sha256);
- g_variant_unref (pdata->meta_data);
- g_free (pdata);
-}
-
-struct _ParsedTreeData {
- GHashTable *files; /* char* filename -> char* checksum */
- GHashTable *directories; /* char* dirname -> ParsedDirectoryData* */
-};
-
-static ParsedTreeData *
-parsed_tree_data_new (void)
-{
- ParsedTreeData *ret = g_new0 (ParsedTreeData, 1);
- ret->files = g_hash_table_new_full (g_str_hash, g_str_equal,
- (GDestroyNotify)g_free,
- (GDestroyNotify)g_free);
- ret->directories = g_hash_table_new_full (g_str_hash, g_str_equal,
- (GDestroyNotify)g_free,
- (GDestroyNotify)parsed_directory_data_free);
- return ret;
-}
-
-static void
-parsed_tree_data_free (ParsedTreeData *pdata)
-{
- if (pdata == NULL)
- return;
- g_hash_table_destroy (pdata->files);
- g_hash_table_destroy (pdata->directories);
- g_free (pdata);
-}
-
-static gboolean
-parse_tree (HacktreeRepo *self,
- const char *sha256,
- ParsedTreeData **out_pdata,
- GError **error)
-{
- gboolean ret = FALSE;
- ParsedTreeData *ret_pdata = NULL;
- int i, n;
- guint32 version;
- GVariant *tree_variant = NULL;
- GVariant *meta_variant = NULL;
- GVariant *files_variant = NULL;
- GVariant *dirs_variant = NULL;
-
- if (!load_gvariant_object (self, HACKTREE_SERIALIZED_TREE_VARIANT,
- sha256, &tree_variant, error))
- goto out;
-
- g_variant_get (tree_variant, "(u@a{sv}@a(ss)@a(sss))",
- &version, &meta_variant, &files_variant, &dirs_variant);
-
- ret_pdata = parsed_tree_data_new ();
- n = g_variant_n_children (files_variant);
- for (i = 0; i < n; i++)
- {
- const char *filename;
- const char *checksum;
-
- g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum);
-
- g_hash_table_insert (ret_pdata->files, g_strdup (filename), g_strdup (checksum));
- }
-
- n = g_variant_n_children (dirs_variant);
- for (i = 0; i < n; i++)
- {
- const char *dirname;
- const char *tree_checksum;
- const char *meta_checksum;
- ParsedTreeData *child_tree = NULL;
- GVariant *metadata = NULL;
- ParsedDirectoryData *child_dir = NULL;
-
- g_variant_get_child (dirs_variant, i, "(sss)",
- &dirname, &tree_checksum, &meta_checksum);
-
- if (!parse_tree (self, tree_checksum, &child_tree, error))
- goto out;
-
- if (!load_gvariant_object (self, HACKTREE_SERIALIZED_DIRMETA_VARIANT,
- meta_checksum, &metadata, error))
- {
- parsed_tree_data_free (child_tree);
- goto out;
- }
-
- child_dir = g_new0 (ParsedDirectoryData, 1);
- child_dir->tree_data = child_tree;
- child_dir->metadata_sha256 = g_strdup (meta_checksum);
- child_dir->meta_data = g_variant_ref_sink (metadata);
-
- g_hash_table_insert (ret_pdata->directories, g_strdup (dirname), child_dir);
- }
-
- ret = TRUE;
- out:
- if (!ret)
- parsed_tree_data_free (ret_pdata);
- else
- *out_pdata = ret_pdata;
- if (tree_variant)
- g_variant_unref (tree_variant);
- if (meta_variant)
- g_variant_unref (meta_variant);
- if (files_variant)
- g_variant_unref (files_variant);
- if (dirs_variant)
- g_variant_unref (dirs_variant);
- return ret;
-}
-
-static gboolean
-load_commit_and_trees (HacktreeRepo *self,
- const char *commit_sha256,
- GVariant **out_commit,
- ParsedDirectoryData **out_root_data,
- GError **error)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- GVariant *ret_commit = NULL;
- ParsedDirectoryData *ret_root_data = NULL;
- ParsedTreeData *tree_data = NULL;
- char *ret_metadata_checksum = NULL;
- GVariant *root_metadata = NULL;
- gboolean ret = FALSE;
- const char *tree_contents_checksum;
- const char *tree_meta_checksum;
-
- if (!priv->current_head)
- {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Can't load current commit; no HEAD reference");
- goto out;
- }
-
- if (!load_gvariant_object (self, HACKTREE_SERIALIZED_COMMIT_VARIANT,
- commit_sha256, &ret_commit, error))
- goto out;
-
- g_variant_get_child (ret_commit, 6, "&s", &tree_contents_checksum);
- g_variant_get_child (ret_commit, 7, "&s", &tree_meta_checksum);
-
- if (!load_gvariant_object (self, HACKTREE_SERIALIZED_DIRMETA_VARIANT,
- tree_meta_checksum, &root_metadata, error))
- goto out;
-
- if (!parse_tree (self, tree_contents_checksum, &tree_data, error))
- goto out;
-
- ret_root_data = g_new0 (ParsedDirectoryData, 1);
- ret_root_data->tree_data = tree_data;
- ret_root_data->metadata_sha256 = g_strdup (tree_meta_checksum);
- ret_root_data->meta_data = root_metadata;
- root_metadata = NULL;
-
- ret = TRUE;
- *out_commit = ret_commit;
- ret_commit = NULL;
- *out_root_data = ret_root_data;
- ret_root_data = NULL;
- out:
- if (ret_commit)
- g_variant_unref (ret_commit);
- parsed_directory_data_free (ret_root_data);
- g_free (ret_metadata_checksum);
- if (root_metadata)
- g_variant_unref (root_metadata);
- return ret;
-}
-
-static GVariant *
-create_empty_gvariant_dict (void)
-{
- GVariantBuilder builder;
- g_variant_builder_init (&builder, G_VARIANT_TYPE("a{sv}"));
- return g_variant_builder_end (&builder);
-}
-
-static gboolean
-import_parsed_tree (HacktreeRepo *self,
- ParsedTreeData *tree,
- GChecksum **out_checksum,
- GError **error)
-{
- gboolean ret = FALSE;
- GVariant *serialized_tree = NULL;
- gboolean builders_initialized = FALSE;
- GVariantBuilder files_builder;
- GVariantBuilder dirs_builder;
- GHashTableIter hash_iter;
- gpointer key, value;
-
- g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(ss)"));
- g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sss)"));
- builders_initialized = TRUE;
-
- g_hash_table_iter_init (&hash_iter, tree->files);
- while (g_hash_table_iter_next (&hash_iter, &key, &value))
- {
- const char *name = key;
- const char *checksum = value;
-
- g_variant_builder_add (&files_builder, "(ss)", name, checksum);
- }
-
- g_hash_table_iter_init (&hash_iter, tree->directories);
- while (g_hash_table_iter_next (&hash_iter, &key, &value))
- {
- const char *name = key;
- GChecksum *dir_checksum = NULL;
- ParsedDirectoryData *dir = value;
-
- if (!import_parsed_tree (self, dir->tree_data, &dir_checksum, error))
- goto out;
-
- g_variant_builder_add (&dirs_builder, "(sss)",
- name, g_checksum_get_string (dir_checksum), dir->metadata_sha256);
- }
-
- serialized_tree = g_variant_new ("(u@a{sv}@a(ss)@a(sss))",
- 0,
- create_empty_gvariant_dict (),
- g_variant_builder_end (&files_builder),
- g_variant_builder_end (&dirs_builder));
- builders_initialized = FALSE;
- g_variant_ref_sink (serialized_tree);
- if (!import_gvariant_object (self, HACKTREE_SERIALIZED_TREE_VARIANT, serialized_tree, out_checksum, error))
- goto out;
-
- ret = TRUE;
- out:
- if (builders_initialized)
- {
- g_variant_builder_clear (&files_builder);
- g_variant_builder_clear (&dirs_builder);
- }
- if (serialized_tree)
- g_variant_unref (serialized_tree);
- return ret;
-}
-
-static gboolean
-check_path (const char *filename,
- GError **error)
-{
- gboolean ret = FALSE;
-
- if (!*filename)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Invalid empty filename");
- goto out;
- }
-
- if (strcmp (filename, ".") == 0)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Self-reference '.' in filename '%s' not allowed (yet)", filename);
- goto out;
- }
-
- if (ht_util_filename_has_dotdot (filename))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Path uplink '..' in filename '%s' not allowed (yet)", filename);
- goto out;
- }
-
- if (g_path_is_absolute (filename))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Absolute filename '%s' not allowed (yet)", filename);
- goto out;
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-static gboolean
-walk_parsed_tree (HacktreeRepo *self,
- const char *filename,
- ParsedTreeData *tree,
- int *out_filename_index, /* out*/
- char **out_component, /* out, must free */
- ParsedTreeData **out_tree, /* out, but do not free */
- GError **error)
-{
- gboolean ret = FALSE;
- GPtrArray *components = NULL;
- ParsedTreeData *current_tree = tree;
- const char *component = NULL;
- const char *file_sha1 = NULL;
- ParsedDirectoryData *dir = NULL;
- int i;
- int ret_filename_index = 0;
-
- components = ht_util_path_split (filename);
- g_assert (components != NULL);
-
- current_tree = tree;
- for (i = 0; i < components->len - 1; i++)
- {
- component = components->pdata[i];
- file_sha1 = g_hash_table_lookup (current_tree->files, component);
- dir = g_hash_table_lookup (current_tree->directories, component);
-
- if (!(file_sha1 || dir))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No such file or directory: %s",
- filename);
- goto out;
- }
- else if (file_sha1)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Encountered non-directory '%s' in '%s'",
- (char*)component,
- filename);
- goto out;
- }
- else
- {
- g_assert (dir != NULL);
- current_tree = dir->tree_data;
- ret_filename_index++;
- }
- }
-
- ret = TRUE;
- *out_filename_index = i;
- *out_component = components->pdata[components->len-1];
- components->pdata[components->len-1] = NULL; /* steal */
- *out_tree = current_tree;
- out:
- g_ptr_array_free (components, TRUE);
- return ret;
-}
-
-static gboolean
-remove_files_from_tree (HacktreeRepo *self,
- const char *base,
- GPtrArray *removed_files,
- ParsedTreeData *tree,
- GError **error)
-{
- gboolean ret = FALSE;
- int i;
-
- for (i = 0; i < removed_files->len; i++)
- {
- const char *filename = removed_files->pdata[i];
- int filename_index;
- char *component = NULL;
- ParsedTreeData *parent;
- const char *file_sha1;
- ParsedTreeData *dir;
-
- if (!check_path (filename, error))
- goto out;
-
- if (!walk_parsed_tree (self, filename, tree,
- &filename_index, (char**)&component, &parent,
- error))
- goto out;
-
- file_sha1 = g_hash_table_lookup (parent->files, component);
- dir = g_hash_table_lookup (parent->directories, component);
-
- if (file_sha1)
- g_hash_table_remove (parent->files, component);
- else if (dir)
- g_hash_table_remove (parent->directories, component);
- else
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No such file or directory: %s",
- filename);
- g_free (component);
- goto out;
- }
- g_free (component);
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-static gboolean
-add_one_directory_to_tree_and_import (HacktreeRepo *self,
- const char *basename,
- const char *abspath,
- ParsedTreeData *tree,
- ParsedDirectoryData **dir, /*inout*/
- GError **error)
-{
- gboolean ret = FALSE;
- GVariant *dirmeta = NULL;
- GChecksum *dir_meta_checksum = NULL;
- ParsedDirectoryData *dir_value = *dir;
-
- g_assert (tree != NULL);
-
- if (!import_directory_meta (self, abspath, &dirmeta, &dir_meta_checksum, error))
- goto out;
-
- if (dir_value)
- {
- g_variant_unref (dir_value->meta_data);
- dir_value->meta_data = dirmeta;
- }
- else
- {
- dir_value = g_new0 (ParsedDirectoryData, 1);
- dir_value->tree_data = parsed_tree_data_new ();
- dir_value->metadata_sha256 = g_strdup (g_checksum_get_string (dir_meta_checksum));
- dir_value->meta_data = dirmeta;
- g_hash_table_insert (tree->directories, g_strdup (basename), dir_value);
- }
-
- ret = TRUE;
- *dir = dir_value;
- out:
- if (dir_meta_checksum)
- g_checksum_free (dir_meta_checksum);
- return ret;
-}
-
-static gboolean
-add_one_file_to_tree_and_import (HacktreeRepo *self,
- const char *basename,
- const char *abspath,
- ParsedTreeData *tree,
- GError **error)
-{
- gboolean ret = FALSE;
- GChecksum *checksum = NULL;
-
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
- g_assert (tree != NULL);
-
- if (!link_one_file (self, abspath, HACKTREE_OBJECT_TYPE_FILE,
- TRUE, FALSE, &checksum, error))
- goto out;
-
- g_hash_table_replace (tree->files, g_strdup (basename),
- g_strdup (g_checksum_get_string (checksum)));
-
- ret = TRUE;
- out:
- if (checksum)
- g_checksum_free (checksum);
- return ret;
-}
-
-static gboolean
-add_one_path_to_tree_and_import (HacktreeRepo *self,
- const char *base,
- const char *filename,
- ParsedTreeData *tree,
- GError **error)
-{
- gboolean ret = FALSE;
- GPtrArray *components = NULL;
- struct stat stbuf;
- char *component_abspath = NULL;
- ParsedTreeData *current_tree = tree;
- const char *component = NULL;
- const char *file_sha1;
- char *abspath = NULL;
- ParsedDirectoryData *dir;
- int i;
- gboolean is_directory;
-
- if (!check_path (filename, error))
- goto out;
-
- abspath = g_build_filename (base, filename, NULL);
-
- if (lstat (abspath, &stbuf) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
- is_directory = S_ISDIR(stbuf.st_mode);
-
- if (components)
- g_ptr_array_free (components, TRUE);
- components = ht_util_path_split (filename);
- g_assert (components->len > 0);
-
- current_tree = tree;
- for (i = 0; i < components->len; i++)
- {
- component = components->pdata[i];
- g_free (component_abspath);
- component_abspath = ht_util_path_join_n (base, components, i);
- file_sha1 = g_hash_table_lookup (current_tree->files, component);
- dir = g_hash_table_lookup (current_tree->directories, component);
-
- g_assert_cmpstr (component, !=, ".");
-
- if (i < components->len - 1)
- {
- if (file_sha1 != NULL)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Encountered non-directory '%s' in '%s'",
- component,
- filename);
- goto out;
- }
- /* Implicitly add intermediate directories */
- if (!add_one_directory_to_tree_and_import (self, component,
- component_abspath, current_tree, &dir,
- error))
- goto out;
- g_assert (dir != NULL);
- current_tree = dir->tree_data;
- }
- else if (is_directory)
- {
- if (file_sha1 != NULL)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "File '%s' can't be overwritten by directory",
- filename);
- goto out;
- }
- if (!add_one_directory_to_tree_and_import (self, component,
- abspath, current_tree, &dir,
- error))
- goto out;
- }
- else
- {
- g_assert (!is_directory);
- if (dir != NULL)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "File '%s' can't be overwritten by directory",
- filename);
- goto out;
- }
- if (!add_one_file_to_tree_and_import (self, component, abspath,
- current_tree, error))
- goto out;
- }
- }
-
- ret = TRUE;
- out:
- g_free (component_abspath);
- g_free (abspath);
- return ret;
-}
-
-static gboolean
-add_files_to_tree_and_import (HacktreeRepo *self,
- const char *base,
- GPtrArray *added_files,
- ParsedTreeData *tree,
- GError **error)
-{
- gboolean ret = FALSE;
- int i;
-
- for (i = 0; i < added_files->len; i++)
- {
- const char *path = added_files->pdata[i];
-
- if (!add_one_path_to_tree_and_import (self, base, path, tree, error))
- goto out;
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-static gboolean
-commit_parsed_tree (HacktreeRepo *self,
- const char *subject,
- const char *body,
- GVariant *metadata,
- ParsedDirectoryData *root,
- GChecksum **out_commit,
- GError **error)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- gboolean ret = FALSE;
- GChecksum *root_checksum = NULL;
- GChecksum *ret_commit = NULL;
- GVariant *commit = NULL;
- GDateTime *now = NULL;
-
- if (!import_parsed_tree (self, root->tree_data, &root_checksum, error))
- goto out;
-
- now = g_date_time_new_now_utc ();
- commit = g_variant_new ("(u@a{sv}ssstss)",
- HACKTREE_COMMIT_VERSION,
- create_empty_gvariant_dict (),
- priv->current_head ? priv->current_head : "",
- subject, body ? body : "",
- g_date_time_to_unix (now) / G_TIME_SPAN_SECOND,
- g_checksum_get_string (root_checksum),
- root->metadata_sha256);
- if (!import_gvariant_object (self, HACKTREE_SERIALIZED_COMMIT_VARIANT,
- commit, &ret_commit, error))
- goto out;
-
- if (!write_checksum_file (priv->head_ref_path, g_checksum_get_string (ret_commit), error))
- goto out;
-
- g_free (priv->current_head);
- priv->current_head = g_strdup (g_checksum_get_string (ret_commit));
-
- ret = TRUE;
- *out_commit = ret_commit;
- out:
- if (root_checksum)
- g_checksum_free (root_checksum);
- if (commit)
- g_variant_unref (commit);
- if (now)
- g_date_time_unref (now);
- return ret;
-}
-
-static gboolean
-import_root (HacktreeRepo *self,
- const char *base,
- ParsedDirectoryData **out_root,
- GError **error)
-{
- gboolean ret = FALSE;
- ParsedDirectoryData *ret_root = NULL;
- GVariant *root_metadata = NULL;
- GChecksum *root_meta_checksum = NULL;
-
- if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
- goto out;
-
- ret_root = g_new0 (ParsedDirectoryData, 1);
- ret_root->tree_data = parsed_tree_data_new ();
- ret_root->meta_data = root_metadata;
- root_metadata = NULL;
- ret_root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
-
- ret = TRUE;
- *out_root = ret_root;
- ret_root = NULL;
- out:
- if (root_metadata)
- g_variant_unref (root_metadata);
- if (root_meta_checksum)
- g_checksum_free (root_meta_checksum);
- parsed_directory_data_free (ret_root);
- return ret;
-}
-
-gboolean
-hacktree_repo_commit (HacktreeRepo *self,
- const char *subject,
- const char *body,
- GVariant *metadata,
- const char *base,
- GPtrArray *modified_files,
- GPtrArray *removed_files,
- GChecksum **out_commit,
- GError **error)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- gboolean ret = FALSE;
- ParsedDirectoryData *root = NULL;
- GVariant *previous_commit = NULL;
- GChecksum *ret_commit_checksum = NULL;
- char *orig_root_metadata_sha256 = NULL;
- GVariant *root_metadata = NULL;
- GChecksum *root_meta_checksum = NULL;
-
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
- g_return_val_if_fail (priv->inited, FALSE);
-
- if (priv->current_head)
- {
- if (!load_commit_and_trees (self, priv->current_head, &previous_commit, &root, error))
- goto out;
- if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
- goto out;
- g_variant_unref (root->meta_data);
- root->meta_data = root_metadata;
- root_metadata = NULL;
- root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
- }
- else
- {
- /* Initial commit */
- if (!import_root (self, base, &root, error))
- goto out;
- }
-
- if (!remove_files_from_tree (self, base, removed_files, root->tree_data, error))
- goto out;
-
- if (!add_files_to_tree_and_import (self, base, modified_files, root->tree_data, error))
- goto out;
-
- if (!commit_parsed_tree (self, subject, body, metadata, root,
- &ret_commit_checksum, error))
- goto out;
-
- ret = TRUE;
- out:
- if (!ret)
- {
- if (ret_commit_checksum)
- g_checksum_free (ret_commit_checksum);
- }
- else
- {
- *out_commit = ret_commit_checksum;
- }
- if (previous_commit)
- g_variant_unref (previous_commit);
- parsed_directory_data_free (root);
- if (root_metadata)
- g_variant_unref (root_metadata);
- if (root_meta_checksum)
- g_checksum_free (root_meta_checksum);
- return ret;
-}
-
-gboolean
-hacktree_repo_commit_from_filelist_fd (HacktreeRepo *self,
- const char *subject,
- const char *body,
- GVariant *metadata,
- const char *base,
- int fd,
- char separator,
- GChecksum **out_commit,
- GError **error)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- gboolean ret = FALSE;
- ParsedDirectoryData *root = NULL;
- GVariant *previous_commit = NULL;
- GChecksum *ret_commit_checksum = NULL;
- GUnixInputStream *in = NULL;
- GDataInputStream *datain = NULL;
- char *filename = NULL;
- gsize filename_len;
- GError *temp_error = NULL;
- GVariant *root_metadata = NULL;
- GChecksum *root_meta_checksum = NULL;
-
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
- g_return_val_if_fail (priv->inited, FALSE);
-
- /* We're overwriting the tree */
- if (!import_root (self, base, &root, error))
- goto out;
-
- in = (GUnixInputStream*)g_unix_input_stream_new (fd, FALSE);
- datain = g_data_input_stream_new ((GInputStream*)in);
-
- while ((filename = g_data_input_stream_read_upto (datain, &separator, 1,
- &filename_len, NULL, &temp_error)) != NULL)
- {
- if (!g_data_input_stream_read_byte (datain, NULL, &temp_error))
- {
- if (temp_error != NULL)
- {
- g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
- goto out;
- }
- }
- if (!add_one_path_to_tree_and_import (self, base, filename, root->tree_data, error))
- goto out;
- g_free (filename);
- filename = NULL;
- }
- if (filename == NULL && temp_error != NULL)
- {
- g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
- goto out;
- }
- if (!commit_parsed_tree (self, subject, body, metadata,
- root, &ret_commit_checksum, error))
- goto out;
-
- ret = TRUE;
- out:
- if (!ret)
- {
- if (ret_commit_checksum)
- g_checksum_free (ret_commit_checksum);
- }
- else
- {
- *out_commit = ret_commit_checksum;
- }
- if (root_metadata)
- g_variant_unref (root_metadata);
- if (root_meta_checksum)
- g_checksum_free (root_meta_checksum);
- g_clear_object (&datain);
- g_clear_object (&in);
- g_free (filename);
- parsed_directory_data_free (root);
- return ret;
-
-}
-
-static gboolean
-iter_object_dir (HacktreeRepo *self,
- GFile *dir,
- HacktreeRepoObjectIter callback,
- gpointer user_data,
- GError **error)
-{
- gboolean ret = FALSE;
- GError *temp_error = NULL;
- GFileEnumerator *enumerator = NULL;
- GFileInfo *file_info = NULL;
- char *dirpath = NULL;
-
- dirpath = g_file_get_path (dir);
-
- enumerator = g_file_enumerate_children (dir, "standard::name,standard::type,unix::*",
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- NULL,
- error);
- if (!enumerator)
- goto out;
-
- while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
- {
- const char *name;
- guint32 type;
- name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
- type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
-
- if (type != G_FILE_TYPE_DIRECTORY
- && (g_str_has_suffix (name, ".meta")
- || g_str_has_suffix (name, ".file")))
- {
- char *dot;
- char *path;
-
- dot = strrchr (name, '.');
- g_assert (dot);
-
- if ((dot - name) == 62)
- {
- path = g_build_filename (dirpath, name, NULL);
- callback (self, path, file_info, user_data);
- g_free (path);
- }
- }
-
- g_object_unref (file_info);
- }
- if (file_info == NULL && temp_error != NULL)
- {
- g_propagate_error (error, temp_error);
- goto out;
- }
- if (!g_file_enumerator_close (enumerator, NULL, error))
- goto out;
-
- ret = TRUE;
- out:
- g_free (dirpath);
- return ret;
-}
-
-gboolean
-hacktree_repo_iter_objects (HacktreeRepo *self,
- HacktreeRepoObjectIter callback,
- gpointer user_data,
- GError **error)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- GFile *objectdir = NULL;
- GFileEnumerator *enumerator = NULL;
- gboolean ret = FALSE;
- GFileInfo *file_info = NULL;
- GError *temp_error = NULL;
-
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
- g_return_val_if_fail (priv->inited, FALSE);
-
- objectdir = ht_util_new_file_for_path (priv->objects_path);
- enumerator = g_file_enumerate_children (objectdir, "standard::name,standard::type,unix::*",
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- NULL,
- error);
- if (!enumerator)
- goto out;
-
- while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
- {
- const char *name;
- guint32 type;
-
- name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
- type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
-
- if (strlen (name) == 2 && type == G_FILE_TYPE_DIRECTORY)
- {
- GFile *objdir = g_file_get_child (objectdir, name);
- if (!iter_object_dir (self, objdir, callback, user_data, error))
- {
- g_object_unref (objdir);
- goto out;
- }
- g_object_unref (objdir);
- }
- g_object_unref (file_info);
- }
- if (file_info == NULL && temp_error != NULL)
- {
- g_propagate_error (error, temp_error);
- goto out;
- }
- if (!g_file_enumerator_close (enumerator, NULL, error))
- goto out;
-
- ret = TRUE;
- out:
- g_clear_object (&file_info);
- g_clear_object (&enumerator);
- g_clear_object (&objectdir);
- return ret;
-}
-
-gboolean
-hacktree_repo_load_variant (HacktreeRepo *repo,
- const char *sha256,
- HacktreeSerializedVariantType *out_type,
- GVariant **out_variant,
- GError **error)
-{
- gboolean ret = FALSE;
- HacktreeSerializedVariantType ret_type;
- GVariant *ret_variant = NULL;
-
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
- if (!load_gvariant_object_unknown (repo, sha256, &ret_type, &ret_variant, error))
- goto out;
-
- ret = TRUE;
- *out_type = ret_type;
- *out_variant = ret_variant;
- out:
- if (!ret)
- {
- if (ret_variant)
- g_variant_unref (ret_variant);
- g_prefix_error (error, "Failed to load metadata variant '%s': ", sha256);
- }
- return ret;
-}
-
-const char *
-hacktree_repo_get_head (HacktreeRepo *self)
-{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
- g_return_val_if_fail (priv->inited, NULL);
-
- return priv->current_head;
-}
-
-static gboolean
-resolve_ref (HacktreeRepo *self,
- const char *ref,
- char **resolved,
- GError **error)
-{
- if (strcmp (ref, "HEAD") == 0)
- {
- *resolved = g_strdup (hacktree_repo_get_head (self));
- return TRUE;
- }
- else if (strlen (ref) == 64)
- {
- *resolved = g_strdup (ref);
- return TRUE;
- }
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Invalid ref '%s' (must be SHA256 or HEAD)", ref);
- return FALSE;
-}
-
-static gboolean
-checkout_tree (HacktreeRepo *self,
- ParsedTreeData *tree,
- const char *destination,
- GError **error);
-
-static gboolean
-checkout_one_directory (HacktreeRepo *self,
- const char *destination,
- const char *dirname,
- ParsedDirectoryData *dir,
- GError **error)
-{
- gboolean ret = FALSE;
- char *dest_path = NULL;
- guint32 version, uid, gid, mode;
- GVariant *xattr_variant = NULL;
- const guint8 *xattrs = NULL;
- gsize xattr_len;
-
- dest_path = g_build_filename (destination, dirname, NULL);
-
- g_variant_get (dir->meta_data, "(uuuu@a(ayay))",
- &version, &uid, &gid, &mode,
- &xattr_variant);
-
- if (mkdir (dest_path, (mode_t)mode) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- g_prefix_error (error, "Failed to create directory '%s': ", dest_path);
- goto out;
- }
-
- if (!checkout_tree (self, dir->tree_data, dest_path, error))
- goto out;
-
- /* TODO - xattrs */
-
- ret = TRUE;
- out:
- g_free (dest_path);
- g_variant_unref (xattr_variant);
- return ret;
-}
-
-static gboolean
-checkout_tree (HacktreeRepo *self,
- ParsedTreeData *tree,
- const char *destination,
- GError **error)
-{
- gboolean ret = FALSE;
- GHashTableIter hash_iter;
- gpointer key, value;
-
- g_hash_table_iter_init (&hash_iter, tree->files);
- while (g_hash_table_iter_next (&hash_iter, &key, &value))
- {
- const char *filename = key;
- const char *checksum = value;
- char *object_path;
- char *dest_path;
-
- object_path = get_object_path (self, checksum, HACKTREE_OBJECT_TYPE_FILE);
- dest_path = g_build_filename (destination, filename, NULL);
- if (link (object_path, dest_path) < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- g_free (object_path);
- g_free (dest_path);
- goto out;
- }
- g_free (object_path);
- g_free (dest_path);
- }
-
- g_hash_table_iter_init (&hash_iter, tree->directories);
- while (g_hash_table_iter_next (&hash_iter, &key, &value))
- {
- const char *dirname = key;
- ParsedDirectoryData *dir = value;
-
- if (!checkout_one_directory (self, destination, dirname, dir, error))
- goto out;
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-gboolean
-hacktree_repo_checkout (HacktreeRepo *self,
- const char *ref,
- const char *destination,
- GError **error)
-{
- gboolean ret = FALSE;
- GVariant *commit = NULL;
- char *resolved = NULL;
- char *root_meta_sha = NULL;
- ParsedDirectoryData *root = NULL;
-
- if (g_file_test (destination, G_FILE_TEST_EXISTS))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Destination path '%s' already exists",
- destination);
- goto out;
- }
-
- if (!resolve_ref (self, ref, &resolved, error))
- goto out;
-
- if (!load_commit_and_trees (self, resolved, &commit, &root, error))
- goto out;
-
- if (!checkout_one_directory (self, destination, NULL, root, error))
- goto out;
-
- ret = TRUE;
- out:
- g_free (resolved);
- if (commit)
- g_variant_unref (commit);
- parsed_directory_data_free (root);
- g_free (root_meta_sha);
- return ret;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-/* hacktree-repo.h */
-
-#ifndef _HACKTREE_REPO
-#define _HACKTREE_REPO
-
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-#define HACKTREE_TYPE_REPO hacktree_repo_get_type()
-#define HACKTREE_REPO(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), HACKTREE_TYPE_REPO, HacktreeRepo))
-#define HACKTREE_REPO_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST ((klass), HACKTREE_TYPE_REPO, HacktreeRepoClass))
-#define HACKTREE_IS_REPO(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), HACKTREE_TYPE_REPO))
-#define HACKTREE_IS_REPO_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE ((klass), HACKTREE_TYPE_REPO))
-#define HACKTREE_REPO_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), HACKTREE_TYPE_REPO, HacktreeRepoClass))
-
-typedef struct {
- GObject parent;
-} HacktreeRepo;
-
-typedef struct {
- GObjectClass parent_class;
-} HacktreeRepoClass;
-
-GType hacktree_repo_get_type (void);
-
-HacktreeRepo* hacktree_repo_new (const char *path);
-
-gboolean hacktree_repo_check (HacktreeRepo *self, GError **error);
-
-gboolean hacktree_repo_link_file (HacktreeRepo *self,
- const char *path,
- gboolean ignore_exists,
- gboolean force,
- GError **error);
-
-const char * hacktree_repo_get_head (HacktreeRepo *self);
-
-gboolean hacktree_repo_load_variant (HacktreeRepo *self,
- const char *sha256,
- HacktreeSerializedVariantType *out_type,
- GVariant **out_variant,
- GError **error);
-
-gboolean hacktree_repo_commit (HacktreeRepo *self,
- const char *subject,
- const char *body,
- GVariant *metadata,
- const char *base,
- GPtrArray *modified_files,
- GPtrArray *removed_files,
- GChecksum **out_commit,
- GError **error);
-
-gboolean hacktree_repo_commit_from_filelist_fd (HacktreeRepo *self,
- const char *subject,
- const char *body,
- GVariant *metadata,
- const char *base,
- int fd,
- char separator,
- GChecksum **out_commit,
- GError **error);
-
-gboolean hacktree_repo_checkout (HacktreeRepo *self,
- const char *ref,
- const char *destination,
- GError **error);
-
-typedef void (*HacktreeRepoObjectIter) (HacktreeRepo *self, const char *path,
- GFileInfo *fileinfo, gpointer user_data);
-
-gboolean hacktree_repo_iter_objects (HacktreeRepo *self,
- HacktreeRepoObjectIter callback,
- gpointer user_data,
- GError **error);
-
-G_END_DECLS
-
-#endif /* _HACKTREE_REPO */
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_TYPES_H__
-#define __HACKTREE_TYPES_H__
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-#define HACKTREE_REPO_DIR ".ht"
-
-G_END_DECLS
-
-#endif
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_H__
-
-#include <hacktree-core.h>
-#include <hacktree-repo.h>
-#include <hacktree-types.h>
-
-#endif
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include <glib-unix.h>
-#include <gio/gio.h>
-#include <gio/gunixinputstream.h>
-
-#include <string.h>
-
-#include "htutil.h"
-
-gboolean
-ht_util_ensure_directory (const char *path, gboolean with_parents, GError **error)
-{
- GFile *dir;
- GError *temp_error = NULL;
- gboolean ret = FALSE;
-
- dir = g_file_new_for_path (path);
- if (with_parents)
- ret = g_file_make_directory_with_parents (dir, NULL, &temp_error);
- else
- ret = g_file_make_directory (dir, NULL, &temp_error);
- if (!ret)
- {
- if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
- {
- g_propagate_error (error, temp_error);
- goto out;
- }
- else
- g_clear_error (&temp_error);
- }
-
- ret = TRUE;
- out:
- g_clear_object (&dir);
- return ret;
-}
-
-
-char *
-ht_util_get_file_contents_utf8 (const char *path,
- GError **error)
-{
- char *contents;
- gsize len;
- if (!g_file_get_contents (path, &contents, &len, error))
- return NULL;
- if (!g_utf8_validate (contents, len, NULL))
- {
- g_free (contents);
- g_set_error (error,
- G_IO_ERROR,
- G_IO_ERROR_FAILED,
- "File %s contains invalid UTF-8",
- path);
- return NULL;
- }
- return contents;
-}
-
-GInputStream *
-ht_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error)
-{
- GInputStream *ret = NULL;
- int fd;
- int flags = O_RDONLY;
- char *path = NULL;
-
- path = g_file_get_path (file);
-#ifdef O_NOATIME
- flags |= O_NOATIME;
-#endif
- fd = open (path, flags);
- if (fd < 0)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- ret = (GInputStream*)g_unix_input_stream_new (fd, TRUE);
-
- out:
- g_free (path);
- return ret;
-}
-
-/* Like g_file_new_for_path, but only do local stuff, not GVFS */
-GFile *
-ht_util_new_file_for_path (const char *path)
-{
- return g_vfs_get_file_for_path (g_vfs_get_local (), path);
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_GIO_UTILS_H__
-#define __HACKTREE_GIO_UTILS_H__
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-GFile *ht_util_new_file_for_path (const char *path);
-
-gboolean ht_util_ensure_directory (const char *path, gboolean with_parents, GError **error);
-
-char * ht_util_get_file_contents_utf8 (const char *path, GError **error);
-
-GInputStream *ht_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error);
-
-G_END_DECLS
-
-#endif
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-unix-utils.h"
-
-#include <glib-unix.h>
-#include <gio/gio.h>
-#include <gio/gunixoutputstream.h>
-
-#include <string.h>
-#include <sys/types.h>
-#include <dirent.h>
-
-gboolean
-ht_util_spawn_pager (GOutputStream **out_stream,
- GError **error)
-{
- const char *pager;
- char *argv[2];
- int stdin_fd;
- pid_t pid;
- gboolean ret = FALSE;
- GOutputStream *ret_stream = NULL;
-
- if (!isatty (1))
- {
- ret_stream = (GOutputStream*)g_unix_output_stream_new (1, TRUE);
- }
- else
- {
- pager = g_getenv ("GIT_PAGER");
- if (pager == NULL)
- pager = "less";
-
- argv[0] = (char*)pager;
- argv[1] = NULL;
-
- if (!g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
- NULL, NULL, &pid, &stdin_fd, NULL, NULL, error))
- {
- g_prefix_error (error, "%s", "Failed to spawn pager: ");
- goto out;
- }
-
- ret_stream = (GOutputStream*)g_unix_output_stream_new (stdin_fd, TRUE);
- }
-
- *out_stream = ret_stream;
- ret_stream = NULL;
- ret = TRUE;
- out:
- g_clear_object (&ret_stream);
- return ret;
-}
-
-static int
-compare_filenames_by_component_length (const char *a,
- const char *b)
-{
- char *a_slash, *b_slash;
-
- a_slash = strchr (a, '/');
- b_slash = strchr (b, '/');
- while (a_slash && b_slash)
- {
- a = a_slash + 1;
- b = b_slash + 1;
- a_slash = strchr (a, '/');
- b_slash = strchr (b, '/');
- }
- if (a_slash)
- return -1;
- else if (b_slash)
- return 1;
- else
- return 0;
-}
-
-GPtrArray *
-ht_util_sort_filenames_by_component_length (GPtrArray *files)
-{
- GPtrArray *array = g_ptr_array_sized_new (files->len);
- memcpy (array->pdata, files->pdata, sizeof (gpointer) * files->len);
- g_ptr_array_sort (array, (GCompareFunc) compare_filenames_by_component_length);
- return array;
-}
-
-int
-ht_util_count_filename_components (const char *path)
-{
- int i = 0;
-
- while (path)
- {
- i++;
- path = strchr (path, '/');
- if (path)
- path++;
- }
- return i;
-}
-
-gboolean
-ht_util_filename_has_dotdot (const char *path)
-{
- char *p;
- char last;
-
- if (strcmp (path, "..") == 0)
- return TRUE;
- if (g_str_has_prefix (path, "../"))
- return TRUE;
- p = strstr (path, "/..");
- if (!p)
- return FALSE;
- last = *(p + 1);
- return last == '\0' || last == '/';
-}
-
-GPtrArray *
-ht_util_path_split (const char *path)
-{
- GPtrArray *ret = NULL;
- const char *p;
- const char *slash;
- int i;
-
- g_return_val_if_fail (path[0] != '/', NULL);
-
- ret = g_ptr_array_new ();
- g_ptr_array_set_free_func (ret, g_free);
-
- p = path;
- do {
- slash = strchr (p, '/');
- if (!slash)
- {
- g_ptr_array_add (ret, g_strdup (p));
- p = NULL;
- }
- else
- {
- g_ptr_array_add (ret, g_strndup (p, slash - p));
- p = slash + 1;
- }
- } while (p && *p);
-
- /* Canonicalize by removing duplicate '.' */
- for (i = ret->len-1; i >= 0; i--)
- {
- if (strcmp (ret->pdata[i], ".") == 0)
- g_ptr_array_remove_index (ret, i);
- }
-
- return ret;
-}
-
-char *
-ht_util_path_join_n (const char *base, GPtrArray *components, int n)
-{
- int max = MIN(n+1, components->len);
- GPtrArray *subcomponents;
- char *path;
- int i;
-
- subcomponents = g_ptr_array_new ();
-
- if (base != NULL)
- g_ptr_array_add (subcomponents, (char*)base);
-
- for (i = 0; i < max; i++)
- {
- g_ptr_array_add (subcomponents, components->pdata[i]);
- }
- g_ptr_array_add (subcomponents, NULL);
-
- path = g_build_filenamev ((char**)subcomponents->pdata);
- g_ptr_array_free (subcomponents, TRUE);
-
- return path;
-}
-
-void
-ht_util_set_error_from_errno (GError **error,
- gint saved_errno)
-{
- g_set_error_literal (error,
- G_UNIX_ERROR,
- 0,
- g_strerror (saved_errno));
- errno = saved_errno;
-}
-
-int
-ht_util_open_file_read (const char *path, GError **error)
-{
- char *dirname = NULL;
- char *basename = NULL;
- DIR *dir = NULL;
- int fd = -1;
-
- dirname = g_path_get_dirname (path);
- basename = g_path_get_basename (path);
- dir = opendir (dirname);
- if (dir == NULL)
- {
- ht_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- fd = ht_util_open_file_read_at (dirfd (dir), basename, error);
-
- out:
- g_free (basename);
- g_free (dirname);
- if (dir != NULL)
- closedir (dir);
- return fd;
-}
-
-int
-ht_util_open_file_read_at (int dirfd, const char *name, GError **error)
-{
- int fd;
- int flags = O_RDONLY;
-
-#ifdef O_CLOEXEC
- flags |= O_CLOEXEC;
-#endif
-#ifdef O_NOATIME
- flags |= O_NOATIME;
-#endif
- fd = openat (dirfd, name, flags);
- if (fd < 0)
- ht_util_set_error_from_errno (error, errno);
- return fd;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_UNIX_UTILS_H__
-#define __HACKTREE_UNIX_UTILS_H__
-
-#include <gio/gio.h>
-#include <glib-unix.h>
-
-/* I just put all this shit here. Sue me. */
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <string.h>
-#include <fcntl.h>
-#include <stdio.h>
-
-G_BEGIN_DECLS
-
-gboolean ht_util_spawn_pager (GOutputStream **out_stream, GError **error);
-
-gboolean ht_util_filename_has_dotdot (const char *path);
-
-GPtrArray *ht_util_sort_filenames_by_component_length (GPtrArray *files);
-
-GPtrArray* ht_util_path_split (const char *path);
-
-char *ht_util_path_join_n (const char *base, GPtrArray *components, int n);
-
-int ht_util_count_filename_components (const char *path);
-
-int ht_util_open_file_read (const char *path, GError **error);
-
-int ht_util_open_file_read_at (int dirfd, const char *name, GError **error);
-
-void ht_util_set_error_from_errno (GError **error, gint saved_errno);
-
-G_END_DECLS
-
-#endif
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_UTIL_H__
-
-#include <ht-unix-utils.h>
-#include <ht-gio-utils.h>
-
-#endif
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ostree.h"
+#include "otutil.h"
+
+#include <sys/types.h>
+#include <attr/xattr.h>
+
+static char *
+stat_to_string (struct stat *stbuf)
+{
+ return g_strdup_printf ("%u:%u:%u",
+ (guint32)(stbuf->st_mode & ~S_IFMT),
+ (guint32)stbuf->st_uid,
+ (guint32)stbuf->st_gid);
+}
+
+static char *
+canonicalize_xattrs (char *xattr_string, size_t len)
+{
+ char *p;
+ GSList *xattrs = NULL;
+ GSList *iter;
+ GString *result;
+
+ result = g_string_new (0);
+
+ p = xattr_string;
+ while (p < xattr_string+len)
+ {
+ xattrs = g_slist_prepend (xattrs, p);
+ p += strlen (p) + 1;
+ }
+
+ xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
+ for (iter = xattrs; iter; iter = iter->next)
+ g_string_append (result, iter->data);
+
+ g_slist_free (xattrs);
+ return g_string_free (result, FALSE);
+}
+
+static gboolean
+read_xattr_name_array (const char *path,
+ const char *xattrs,
+ size_t len,
+ GVariantBuilder *builder,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ const char *p;
+
+ p = xattrs;
+ while (p < xattrs+len)
+ {
+ ssize_t bytes_read;
+ char *buf;
+
+ bytes_read = lgetxattr (path, p, NULL, 0);
+ if (bytes_read < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ if (bytes_read == 0)
+ continue;
+
+ buf = g_malloc (bytes_read);
+ if (lgetxattr (path, p, buf, bytes_read) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ g_free (buf);
+ goto out;
+ }
+
+ g_variant_builder_add (builder, "(@ay@ay)",
+ g_variant_new_bytestring (p),
+ g_variant_new_fixed_array (G_VARIANT_TYPE ("y"), buf, bytes_read, 1));
+
+ g_free (buf);
+ p = p + strlen (p) + 1;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+GVariant *
+ostree_get_xattrs_for_path (const char *path,
+ GError **error)
+{
+ GVariant *ret = NULL;
+ GVariantBuilder builder;
+ char *xattr_names = NULL;
+ char *xattr_names_canonical = NULL;
+ ssize_t bytes_read;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
+
+ bytes_read = llistxattr (path, NULL, 0);
+
+ if (bytes_read < 0)
+ {
+ if (errno != ENOTSUP)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ }
+ else if (bytes_read > 0)
+ {
+ const char *p;
+ xattr_names = g_malloc (bytes_read);
+ if (llistxattr (path, xattr_names, bytes_read) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read);
+
+ if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error))
+ goto out;
+ }
+
+ ret = g_variant_builder_end (&builder);
+ out:
+ if (!ret)
+ g_variant_builder_clear (&builder);
+ g_free (xattr_names);
+ g_free (xattr_names_canonical);
+ return ret;
+}
+
+gboolean
+ostree_stat_and_checksum_file (int dir_fd, const char *path,
+ GChecksum **out_checksum,
+ struct stat *out_stbuf,
+ GError **error)
+{
+ GChecksum *content_sha256 = NULL;
+ GChecksum *content_and_meta_sha256 = NULL;
+ char *stat_string = NULL;
+ ssize_t bytes_read;
+ GVariant *xattrs = NULL;
+ int fd = -1;
+ DIR *temp_dir = NULL;
+ char *basename = NULL;
+ gboolean ret = FALSE;
+ char *symlink_target = NULL;
+ char *device_id = NULL;
+ struct stat stbuf;
+
+ basename = g_path_get_basename (path);
+
+ if (dir_fd == -1)
+ {
+ char *dirname = g_path_get_dirname (path);
+ temp_dir = opendir (dirname);
+ if (temp_dir == NULL)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ g_free (dirname);
+ }
+ g_free (dirname);
+ dir_fd = dirfd (temp_dir);
+ }
+
+ if (fstatat (dir_fd, basename, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ if (!S_ISLNK(stbuf.st_mode))
+ {
+ fd = ot_util_open_file_read_at (dir_fd, basename, error);
+ if (fd < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ }
+
+ stat_string = stat_to_string (&stbuf);
+ xattrs = ostree_get_xattrs_for_path (path, error);
+ if (!xattrs)
+ goto out;
+
+ content_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
+
+ if (S_ISREG(stbuf.st_mode))
+ {
+ guint8 buf[8192];
+
+ while ((bytes_read = read (fd, buf, sizeof (buf))) > 0)
+ g_checksum_update (content_sha256, buf, bytes_read);
+ if (bytes_read < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ }
+ else if (S_ISLNK(stbuf.st_mode))
+ {
+ symlink_target = g_malloc (PATH_MAX);
+
+ if (readlinkat (dir_fd, basename, symlink_target, PATH_MAX) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ g_checksum_update (content_sha256, (guint8*)symlink_target, strlen (symlink_target));
+ }
+ else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode))
+ {
+ device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev);
+ g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id));
+ }
+ else
+ {
+ g_set_error (error, G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Unsupported file '%s' (must be regular, symbolic link, or device)",
+ path);
+ goto out;
+ }
+
+ content_and_meta_sha256 = g_checksum_copy (content_sha256);
+
+ g_checksum_update (content_and_meta_sha256, (guint8*)stat_string, strlen (stat_string));
+ g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+
+ *out_stbuf = stbuf;
+ *out_checksum = content_and_meta_sha256;
+ ret = TRUE;
+ out:
+ if (fd >= 0)
+ close (fd);
+ if (temp_dir != NULL)
+ closedir (temp_dir);
+ g_free (symlink_target);
+ g_free (basename);
+ g_free (stat_string);
+ if (xattrs)
+ g_variant_unref (xattrs);
+ if (content_sha256)
+ g_checksum_free (content_sha256);
+
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef _OSTREE_CORE
+#define _OSTREE_CORE
+
+#include <otutil.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_EMPTY_STRING_SHA256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
+typedef enum {
+ OSTREE_OBJECT_TYPE_FILE = 1,
+ OSTREE_OBJECT_TYPE_META = 2,
+} OstreeObjectType;
+
+typedef enum {
+ OSTREE_SERIALIZED_TREE_VARIANT = 1,
+ OSTREE_SERIALIZED_COMMIT_VARIANT = 2,
+ OSTREE_SERIALIZED_DIRMETA_VARIANT = 3,
+ OSTREE_SERIALIZED_XATTR_VARIANT = 4
+} OstreeSerializedVariantType;
+
+#define OSTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
+
+/*
+ * xattr objects:
+ * a(ayay) - array of (name, value) pairs, both binary data, though name is a bytestring
+ */
+#define OSTREE_XATTR_GVARIANT_FORMAT "a(ayay)"
+
+#define OSTREE_DIR_META_VERSION 0
+/*
+ * dirmeta objects:
+ * u - Version
+ * u - uid
+ * u - gid
+ * u - mode
+ * a(ayay) - xattrs
+ */
+#define OSTREE_DIRMETA_GVARIANT_FORMAT "(uuuua(ayay))"
+
+#define OSTREE_TREE_VERSION 0
+/*
+ * Tree objects:
+ * u - Version
+ * a{sv} - Metadata
+ * a(ss) - array of (filename, checksum) for files
+ * a(sss) - array of (dirname, tree_checksum, meta_checksum) for directories
+ */
+#define OSTREE_TREE_GVARIANT_FORMAT "(ua{sv}a(ss)a(sss)"
+
+#define OSTREE_COMMIT_VERSION 0
+/*
+ * Commit objects:
+ * u - Version
+ * a{sv} - Metadata
+ * s - parent checksum (empty string for initial)
+ * s - subject
+ * s - body
+ * t - Timestamp in seconds since the epoch (UTC)
+ * s - Root tree contents
+ * s - Root tree metadata
+ */
+#define OSTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)"
+
+GVariant *ostree_get_xattrs_for_path (const char *path,
+ GError **error);
+
+gboolean ostree_stat_and_checksum_file (int dirfd, const char *path,
+ GChecksum **out_checksum,
+ struct stat *out_stbuf,
+ GError **error);
+
+
+#endif /* _OSTREE_REPO */
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ostree.h"
+#include "otutil.h"
+
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixinputstream.h>
+
+static gboolean
+link_one_file (OstreeRepo *self, const char *path,
+ OstreeObjectType type,
+ gboolean ignore_exists, gboolean force,
+ GChecksum **out_checksum,
+ GError **error);
+static char *
+get_object_path (OstreeRepo *self,
+ const char *checksum,
+ OstreeObjectType type);
+
+enum {
+ PROP_0,
+
+ PROP_PATH
+};
+
+G_DEFINE_TYPE (OstreeRepo, ostree_repo, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), OSTREE_TYPE_REPO, OstreeRepoPrivate))
+
+typedef struct _OstreeRepoPrivate OstreeRepoPrivate;
+
+struct _OstreeRepoPrivate {
+ char *path;
+ GFile *repo_file;
+ char *head_ref_path;
+ char *objects_path;
+
+ gboolean inited;
+ char *current_head;
+};
+
+static void
+ostree_repo_finalize (GObject *object)
+{
+ OstreeRepo *self = OSTREE_REPO (object);
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+ g_free (priv->path);
+ g_clear_object (&priv->repo_file);
+ g_free (priv->head_ref_path);
+ g_free (priv->objects_path);
+ g_free (priv->current_head);
+
+ G_OBJECT_CLASS (ostree_repo_parent_class)->finalize (object);
+}
+
+static void
+ostree_repo_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ OstreeRepo *self = OSTREE_REPO (object);
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+ switch (prop_id)
+ {
+ case PROP_PATH:
+ priv->path = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ostree_repo_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ OstreeRepo *self = OSTREE_REPO (object);
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+ switch (prop_id)
+ {
+ case PROP_PATH:
+ g_value_set_string (value, priv->path);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+ostree_repo_constructor (GType gtype,
+ guint n_properties,
+ GObjectConstructParam *properties)
+{
+ GObject *object;
+ GObjectClass *parent_class;
+ OstreeRepoPrivate *priv;
+
+ parent_class = G_OBJECT_CLASS (ostree_repo_parent_class);
+ object = parent_class->constructor (gtype, n_properties, properties);
+
+ priv = GET_PRIVATE (object);
+
+ g_assert (priv->path != NULL);
+
+ priv->repo_file = ot_util_new_file_for_path (priv->path);
+ priv->head_ref_path = g_build_filename (priv->path, OSTREE_REPO_DIR, "HEAD", NULL);
+ priv->objects_path = g_build_filename (priv->path, OSTREE_REPO_DIR, "objects", NULL);
+
+ return object;
+}
+
+static void
+ostree_repo_class_init (OstreeRepoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (OstreeRepoPrivate));
+
+ object_class->constructor = ostree_repo_constructor;
+ object_class->get_property = ostree_repo_get_property;
+ object_class->set_property = ostree_repo_set_property;
+ object_class->finalize = ostree_repo_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_PATH,
+ g_param_spec_string ("path",
+ "",
+ "",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+ostree_repo_init (OstreeRepo *self)
+{
+}
+
+OstreeRepo*
+ostree_repo_new (const char *path)
+{
+ return g_object_new (OSTREE_TYPE_REPO, "path", path, NULL);
+}
+
+static gboolean
+parse_checksum_file (OstreeRepo *self,
+ const char *path,
+ char **sha256,
+ GError **error)
+{
+ GError *temp_error = NULL;
+ gboolean ret = FALSE;
+ char *ret_sha256 = NULL;
+
+ ret_sha256 = ot_util_get_file_contents_utf8 (path, &temp_error);
+ if (ret_sha256 == NULL)
+ {
+ if (g_error_matches (temp_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ {
+ g_clear_error (&temp_error);
+ }
+ else
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ }
+ else
+ {
+ g_strchomp (ret_sha256);
+ }
+
+ *sha256 = ret_sha256;
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+write_checksum_file (const char *path,
+ const char *sha256,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ char *buf = NULL;
+
+ buf = g_strconcat (sha256, "\n", NULL);
+
+ if (!g_file_set_contents (path, buf, -1, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ g_free (buf);
+ return ret;
+}
+
+gboolean
+ostree_repo_check (OstreeRepo *self, GError **error)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (priv->inited)
+ return TRUE;
+
+ if (!g_file_test (priv->objects_path, G_FILE_TEST_IS_DIR))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Couldn't find objects directory '%s'", priv->objects_path);
+ return FALSE;
+ }
+
+ priv->inited = TRUE;
+
+ return parse_checksum_file (self, priv->head_ref_path, &priv->current_head, error);
+}
+
+static gboolean
+import_gvariant_object (OstreeRepo *self,
+ OstreeSerializedVariantType type,
+ GVariant *variant,
+ GChecksum **out_checksum,
+ GError **error)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+ GVariant *serialized = NULL;
+ gboolean ret = FALSE;
+ gsize bytes_written;
+ char *tmp_name = NULL;
+ int fd = -1;
+ GUnixOutputStream *stream = NULL;
+
+ serialized = g_variant_new ("(uv)", (guint32)type, variant);
+
+ tmp_name = g_build_filename (priv->objects_path, "variant-tmp-XXXXXX", NULL);
+ fd = mkstemp (tmp_name);
+ if (fd < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ stream = (GUnixOutputStream*)g_unix_output_stream_new (fd, FALSE);
+ if (!g_output_stream_write_all ((GOutputStream*)stream,
+ g_variant_get_data (serialized),
+ g_variant_get_size (serialized),
+ &bytes_written,
+ NULL,
+ error))
+ goto out;
+ if (!g_output_stream_close ((GOutputStream*)stream,
+ NULL, error))
+ goto out;
+
+ if (!link_one_file (self, tmp_name, OSTREE_OBJECT_TYPE_META,
+ TRUE, FALSE, out_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ /* Unconditionally unlink; if we suceeded, there's a new link, if not, clean up. */
+ (void) unlink (tmp_name);
+ if (fd != -1)
+ close (fd);
+ if (serialized != NULL)
+ g_variant_unref (serialized);
+ g_free (tmp_name);
+ g_clear_object (&stream);
+ return ret;
+}
+
+static gboolean
+load_gvariant_object_unknown (OstreeRepo *self,
+ const char *sha256,
+ OstreeSerializedVariantType *out_type,
+ GVariant **out_variant,
+ GError **error)
+{
+ GMappedFile *mfile = NULL;
+ gboolean ret = FALSE;
+ GVariant *ret_variant = NULL;
+ GVariant *container = NULL;
+ char *path = NULL;
+ guint32 ret_type;
+
+ path = get_object_path (self, sha256, OSTREE_OBJECT_TYPE_META);
+
+ mfile = g_mapped_file_new (path, FALSE, error);
+ if (mfile == NULL)
+ goto out;
+ else
+ {
+ container = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT),
+ g_mapped_file_get_contents (mfile),
+ g_mapped_file_get_length (mfile),
+ FALSE,
+ (GDestroyNotify) g_mapped_file_unref,
+ mfile);
+ if (!g_variant_is_of_type (container, G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT)))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Corrupted metadata object '%s'", sha256);
+ goto out;
+ }
+ g_variant_get (container, "(uv)",
+ &ret_type, &ret_variant);
+ mfile = NULL;
+ }
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ if (ret_variant)
+ g_variant_unref (ret_variant);
+ }
+ else
+ {
+ *out_type = ret_type;
+ *out_variant = ret_variant;
+ }
+ if (container != NULL)
+ g_variant_unref (container);
+ g_free (path);
+ if (mfile != NULL)
+ g_mapped_file_unref (mfile);
+ return ret;
+}
+
+static gboolean
+load_gvariant_object (OstreeRepo *self,
+ OstreeSerializedVariantType expected_type,
+ const char *sha256,
+ GVariant **out_variant,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ OstreeSerializedVariantType type;
+ GVariant *ret_variant = NULL;
+
+ if (!load_gvariant_object_unknown (self, sha256, &type, &ret_variant, error))
+ goto out;
+
+ if (type != expected_type)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Corrupted metadata object '%s'; found type %u, expected %u", sha256,
+ type, (guint32)expected_type);
+ goto out;
+
+ }
+
+ ret = TRUE;
+ *out_variant = ret_variant;
+ out:
+ if (!ret)
+ {
+ if (ret_variant)
+ g_variant_unref (ret_variant);
+ }
+ return ret;
+}
+
+static gboolean
+import_directory_meta (OstreeRepo *self,
+ const char *path,
+ GVariant **out_variant,
+ GChecksum **out_checksum,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ struct stat stbuf;
+ GChecksum *ret_checksum = NULL;
+ GVariant *dirmeta = NULL;
+ GVariant *xattrs = NULL;
+ gsize xattr_len;
+
+ if (lstat (path, &stbuf) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ if (!S_ISDIR(stbuf.st_mode))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Not a directory: '%s'", path);
+ goto out;
+ }
+
+ xattrs = ostree_get_xattrs_for_path (path, error);
+ if (!xattrs)
+ goto out;
+
+ dirmeta = g_variant_new ("(uuuu@a(ayay))",
+ OSTREE_DIR_META_VERSION,
+ (guint32)stbuf.st_uid,
+ (guint32)stbuf.st_gid,
+ (guint32)(stbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)),
+ xattrs);
+ xattrs = NULL; /* was floating */
+ g_variant_ref_sink (dirmeta);
+
+ if (!import_gvariant_object (self, OSTREE_SERIALIZED_DIRMETA_VARIANT,
+ dirmeta, &ret_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ if (ret_checksum)
+ g_checksum_free (ret_checksum);
+ if (dirmeta != NULL)
+ g_variant_unref (dirmeta);
+ }
+ else
+ {
+ *out_checksum = ret_checksum;
+ *out_variant = dirmeta;
+ }
+ if (xattrs)
+ g_variant_unref (xattrs);
+ return ret;
+}
+
+static char *
+get_object_path (OstreeRepo *self,
+ const char *checksum,
+ OstreeObjectType type)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+ char *checksum_prefix;
+ char *base_path;
+ char *ret;
+ const char *type_string;
+
+ checksum_prefix = g_strndup (checksum, 2);
+ base_path = g_build_filename (priv->objects_path, checksum_prefix, checksum + 2, NULL);
+ switch (type)
+ {
+ case OSTREE_OBJECT_TYPE_FILE:
+ type_string = ".file";
+ break;
+ case OSTREE_OBJECT_TYPE_META:
+ type_string = ".meta";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ ret = g_strconcat (base_path, type_string, NULL);
+ g_free (base_path);
+ g_free (checksum_prefix);
+
+ return ret;
+}
+
+static char *
+prepare_dir_for_checksum_get_object_path (OstreeRepo *self,
+ GChecksum *checksum,
+ OstreeObjectType type,
+ GError **error)
+{
+ char *checksum_dir = NULL;
+ char *object_path = NULL;
+
+ object_path = get_object_path (self, g_checksum_get_string (checksum), type);
+ checksum_dir = g_path_get_dirname (object_path);
+
+ if (!ot_util_ensure_directory (checksum_dir, FALSE, error))
+ goto out;
+
+ out:
+ g_free (checksum_dir);
+ return object_path;
+}
+
+static gboolean
+link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
+ gboolean ignore_exists, gboolean force,
+ GChecksum **out_checksum,
+ GError **error)
+{
+ char *src_basename = NULL;
+ char *src_dirname = NULL;
+ char *dest_basename = NULL;
+ char *tmp_dest_basename = NULL;
+ char *dest_dirname = NULL;
+ GChecksum *id = NULL;
+ DIR *src_dir = NULL;
+ DIR *dest_dir = NULL;
+ gboolean ret = FALSE;
+ struct stat stbuf;
+ char *dest_path = NULL;
+
+ src_basename = g_path_get_basename (path);
+ src_dirname = g_path_get_dirname (path);
+
+ src_dir = opendir (src_dirname);
+ if (src_dir == NULL)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ if (!ostree_stat_and_checksum_file (dirfd (src_dir), path, &id, &stbuf, error))
+ goto out;
+ dest_path = prepare_dir_for_checksum_get_object_path (self, id, type, error);
+ if (!dest_path)
+ goto out;
+
+ dest_basename = g_path_get_basename (dest_path);
+ dest_dirname = g_path_get_dirname (dest_path);
+ dest_dir = opendir (dest_dirname);
+ if (dest_dir == NULL)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ if (force)
+ {
+ tmp_dest_basename = g_strconcat (dest_basename, ".tmp", NULL);
+ (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
+ }
+ else
+ tmp_dest_basename = g_strdup (dest_basename);
+
+ if (linkat (dirfd (src_dir), src_basename, dirfd (dest_dir), tmp_dest_basename, 0) < 0)
+ {
+ if (errno != EEXIST || !ignore_exists)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ }
+
+ if (force)
+ {
+ if (renameat (dirfd (dest_dir), tmp_dest_basename,
+ dirfd (dest_dir), dest_basename) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
+ }
+
+ *out_checksum = id;
+ id = NULL;
+ ret = TRUE;
+ out:
+ if (id != NULL)
+ g_checksum_free (id);
+ if (src_dir != NULL)
+ closedir (src_dir);
+ if (dest_dir != NULL)
+ closedir (dest_dir);
+ g_free (src_basename);
+ g_free (src_dirname);
+ g_free (dest_basename);
+ g_free (tmp_dest_basename);
+ g_free (dest_dirname);
+ return ret;
+}
+
+gboolean
+ostree_repo_link_file (OstreeRepo *self,
+ const char *path,
+ gboolean ignore_exists,
+ gboolean force,
+ GError **error)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+ GChecksum *checksum = NULL;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (priv->inited, FALSE);
+
+ if (!link_one_file (self, path, OSTREE_OBJECT_TYPE_FILE,
+ ignore_exists, force, &checksum, error))
+ return FALSE;
+ g_checksum_free (checksum);
+ return TRUE;
+}
+
+typedef struct _ParsedTreeData ParsedTreeData;
+typedef struct _ParsedDirectoryData ParsedDirectoryData;
+
+static void parsed_tree_data_free (ParsedTreeData *pdata);
+
+struct _ParsedDirectoryData {
+ ParsedTreeData *tree_data;
+ char *metadata_sha256;
+ GVariant *meta_data;
+};
+
+static void
+parsed_directory_data_free (ParsedDirectoryData *pdata)
+{
+ if (pdata == NULL)
+ return;
+ parsed_tree_data_free (pdata->tree_data);
+ g_free (pdata->metadata_sha256);
+ g_variant_unref (pdata->meta_data);
+ g_free (pdata);
+}
+
+struct _ParsedTreeData {
+ GHashTable *files; /* char* filename -> char* checksum */
+ GHashTable *directories; /* char* dirname -> ParsedDirectoryData* */
+};
+
+static ParsedTreeData *
+parsed_tree_data_new (void)
+{
+ ParsedTreeData *ret = g_new0 (ParsedTreeData, 1);
+ ret->files = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)g_free);
+ ret->directories = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)parsed_directory_data_free);
+ return ret;
+}
+
+static void
+parsed_tree_data_free (ParsedTreeData *pdata)
+{
+ if (pdata == NULL)
+ return;
+ g_hash_table_destroy (pdata->files);
+ g_hash_table_destroy (pdata->directories);
+ g_free (pdata);
+}
+
+static gboolean
+parse_tree (OstreeRepo *self,
+ const char *sha256,
+ ParsedTreeData **out_pdata,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ ParsedTreeData *ret_pdata = NULL;
+ int i, n;
+ guint32 version;
+ GVariant *tree_variant = NULL;
+ GVariant *meta_variant = NULL;
+ GVariant *files_variant = NULL;
+ GVariant *dirs_variant = NULL;
+
+ if (!load_gvariant_object (self, OSTREE_SERIALIZED_TREE_VARIANT,
+ sha256, &tree_variant, error))
+ goto out;
+
+ g_variant_get (tree_variant, "(u@a{sv}@a(ss)@a(sss))",
+ &version, &meta_variant, &files_variant, &dirs_variant);
+
+ ret_pdata = parsed_tree_data_new ();
+ n = g_variant_n_children (files_variant);
+ for (i = 0; i < n; i++)
+ {
+ const char *filename;
+ const char *checksum;
+
+ g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum);
+
+ g_hash_table_insert (ret_pdata->files, g_strdup (filename), g_strdup (checksum));
+ }
+
+ n = g_variant_n_children (dirs_variant);
+ for (i = 0; i < n; i++)
+ {
+ const char *dirname;
+ const char *tree_checksum;
+ const char *meta_checksum;
+ ParsedTreeData *child_tree = NULL;
+ GVariant *metadata = NULL;
+ ParsedDirectoryData *child_dir = NULL;
+
+ g_variant_get_child (dirs_variant, i, "(sss)",
+ &dirname, &tree_checksum, &meta_checksum);
+
+ if (!parse_tree (self, tree_checksum, &child_tree, error))
+ goto out;
+
+ if (!load_gvariant_object (self, OSTREE_SERIALIZED_DIRMETA_VARIANT,
+ meta_checksum, &metadata, error))
+ {
+ parsed_tree_data_free (child_tree);
+ goto out;
+ }
+
+ child_dir = g_new0 (ParsedDirectoryData, 1);
+ child_dir->tree_data = child_tree;
+ child_dir->metadata_sha256 = g_strdup (meta_checksum);
+ child_dir->meta_data = g_variant_ref_sink (metadata);
+
+ g_hash_table_insert (ret_pdata->directories, g_strdup (dirname), child_dir);
+ }
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ parsed_tree_data_free (ret_pdata);
+ else
+ *out_pdata = ret_pdata;
+ if (tree_variant)
+ g_variant_unref (tree_variant);
+ if (meta_variant)
+ g_variant_unref (meta_variant);
+ if (files_variant)
+ g_variant_unref (files_variant);
+ if (dirs_variant)
+ g_variant_unref (dirs_variant);
+ return ret;
+}
+
+static gboolean
+load_commit_and_trees (OstreeRepo *self,
+ const char *commit_sha256,
+ GVariant **out_commit,
+ ParsedDirectoryData **out_root_data,
+ GError **error)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+ GVariant *ret_commit = NULL;
+ ParsedDirectoryData *ret_root_data = NULL;
+ ParsedTreeData *tree_data = NULL;
+ char *ret_metadata_checksum = NULL;
+ GVariant *root_metadata = NULL;
+ gboolean ret = FALSE;
+ const char *tree_contents_checksum;
+ const char *tree_meta_checksum;
+
+ if (!priv->current_head)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Can't load current commit; no HEAD reference");
+ goto out;
+ }
+
+ if (!load_gvariant_object (self, OSTREE_SERIALIZED_COMMIT_VARIANT,
+ commit_sha256, &ret_commit, error))
+ goto out;
+
+ g_variant_get_child (ret_commit, 6, "&s", &tree_contents_checksum);
+ g_variant_get_child (ret_commit, 7, "&s", &tree_meta_checksum);
+
+ if (!load_gvariant_object (self, OSTREE_SERIALIZED_DIRMETA_VARIANT,
+ tree_meta_checksum, &root_metadata, error))
+ goto out;
+
+ if (!parse_tree (self, tree_contents_checksum, &tree_data, error))
+ goto out;
+
+ ret_root_data = g_new0 (ParsedDirectoryData, 1);
+ ret_root_data->tree_data = tree_data;
+ ret_root_data->metadata_sha256 = g_strdup (tree_meta_checksum);
+ ret_root_data->meta_data = root_metadata;
+ root_metadata = NULL;
+
+ ret = TRUE;
+ *out_commit = ret_commit;
+ ret_commit = NULL;
+ *out_root_data = ret_root_data;
+ ret_root_data = NULL;
+ out:
+ if (ret_commit)
+ g_variant_unref (ret_commit);
+ parsed_directory_data_free (ret_root_data);
+ g_free (ret_metadata_checksum);
+ if (root_metadata)
+ g_variant_unref (root_metadata);
+ return ret;
+}
+
+static GVariant *
+create_empty_gvariant_dict (void)
+{
+ GVariantBuilder builder;
+ g_variant_builder_init (&builder, G_VARIANT_TYPE("a{sv}"));
+ return g_variant_builder_end (&builder);
+}
+
+static gboolean
+import_parsed_tree (OstreeRepo *self,
+ ParsedTreeData *tree,
+ GChecksum **out_checksum,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GVariant *serialized_tree = NULL;
+ gboolean builders_initialized = FALSE;
+ GVariantBuilder files_builder;
+ GVariantBuilder dirs_builder;
+ GHashTableIter hash_iter;
+ gpointer key, value;
+
+ g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(ss)"));
+ g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sss)"));
+ builders_initialized = TRUE;
+
+ g_hash_table_iter_init (&hash_iter, tree->files);
+ while (g_hash_table_iter_next (&hash_iter, &key, &value))
+ {
+ const char *name = key;
+ const char *checksum = value;
+
+ g_variant_builder_add (&files_builder, "(ss)", name, checksum);
+ }
+
+ g_hash_table_iter_init (&hash_iter, tree->directories);
+ while (g_hash_table_iter_next (&hash_iter, &key, &value))
+ {
+ const char *name = key;
+ GChecksum *dir_checksum = NULL;
+ ParsedDirectoryData *dir = value;
+
+ if (!import_parsed_tree (self, dir->tree_data, &dir_checksum, error))
+ goto out;
+
+ g_variant_builder_add (&dirs_builder, "(sss)",
+ name, g_checksum_get_string (dir_checksum), dir->metadata_sha256);
+ }
+
+ serialized_tree = g_variant_new ("(u@a{sv}@a(ss)@a(sss))",
+ 0,
+ create_empty_gvariant_dict (),
+ g_variant_builder_end (&files_builder),
+ g_variant_builder_end (&dirs_builder));
+ builders_initialized = FALSE;
+ g_variant_ref_sink (serialized_tree);
+ if (!import_gvariant_object (self, OSTREE_SERIALIZED_TREE_VARIANT, serialized_tree, out_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (builders_initialized)
+ {
+ g_variant_builder_clear (&files_builder);
+ g_variant_builder_clear (&dirs_builder);
+ }
+ if (serialized_tree)
+ g_variant_unref (serialized_tree);
+ return ret;
+}
+
+static gboolean
+check_path (const char *filename,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ if (!*filename)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid empty filename");
+ goto out;
+ }
+
+ if (strcmp (filename, ".") == 0)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Self-reference '.' in filename '%s' not allowed (yet)", filename);
+ goto out;
+ }
+
+ if (ot_util_filename_has_dotdot (filename))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Path uplink '..' in filename '%s' not allowed (yet)", filename);
+ goto out;
+ }
+
+ if (g_path_is_absolute (filename))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Absolute filename '%s' not allowed (yet)", filename);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+walk_parsed_tree (OstreeRepo *self,
+ const char *filename,
+ ParsedTreeData *tree,
+ int *out_filename_index, /* out*/
+ char **out_component, /* out, must free */
+ ParsedTreeData **out_tree, /* out, but do not free */
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GPtrArray *components = NULL;
+ ParsedTreeData *current_tree = tree;
+ const char *component = NULL;
+ const char *file_sha1 = NULL;
+ ParsedDirectoryData *dir = NULL;
+ int i;
+ int ret_filename_index = 0;
+
+ components = ot_util_path_split (filename);
+ g_assert (components != NULL);
+
+ current_tree = tree;
+ for (i = 0; i < components->len - 1; i++)
+ {
+ component = components->pdata[i];
+ file_sha1 = g_hash_table_lookup (current_tree->files, component);
+ dir = g_hash_table_lookup (current_tree->directories, component);
+
+ if (!(file_sha1 || dir))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No such file or directory: %s",
+ filename);
+ goto out;
+ }
+ else if (file_sha1)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Encountered non-directory '%s' in '%s'",
+ (char*)component,
+ filename);
+ goto out;
+ }
+ else
+ {
+ g_assert (dir != NULL);
+ current_tree = dir->tree_data;
+ ret_filename_index++;
+ }
+ }
+
+ ret = TRUE;
+ *out_filename_index = i;
+ *out_component = components->pdata[components->len-1];
+ components->pdata[components->len-1] = NULL; /* steal */
+ *out_tree = current_tree;
+ out:
+ g_ptr_array_free (components, TRUE);
+ return ret;
+}
+
+static gboolean
+remove_files_from_tree (OstreeRepo *self,
+ const char *base,
+ GPtrArray *removed_files,
+ ParsedTreeData *tree,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int i;
+
+ for (i = 0; i < removed_files->len; i++)
+ {
+ const char *filename = removed_files->pdata[i];
+ int filename_index;
+ char *component = NULL;
+ ParsedTreeData *parent;
+ const char *file_sha1;
+ ParsedTreeData *dir;
+
+ if (!check_path (filename, error))
+ goto out;
+
+ if (!walk_parsed_tree (self, filename, tree,
+ &filename_index, (char**)&component, &parent,
+ error))
+ goto out;
+
+ file_sha1 = g_hash_table_lookup (parent->files, component);
+ dir = g_hash_table_lookup (parent->directories, component);
+
+ if (file_sha1)
+ g_hash_table_remove (parent->files, component);
+ else if (dir)
+ g_hash_table_remove (parent->directories, component);
+ else
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No such file or directory: %s",
+ filename);
+ g_free (component);
+ goto out;
+ }
+ g_free (component);
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+add_one_directory_to_tree_and_import (OstreeRepo *self,
+ const char *basename,
+ const char *abspath,
+ ParsedTreeData *tree,
+ ParsedDirectoryData **dir, /*inout*/
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GVariant *dirmeta = NULL;
+ GChecksum *dir_meta_checksum = NULL;
+ ParsedDirectoryData *dir_value = *dir;
+
+ g_assert (tree != NULL);
+
+ if (!import_directory_meta (self, abspath, &dirmeta, &dir_meta_checksum, error))
+ goto out;
+
+ if (dir_value)
+ {
+ g_variant_unref (dir_value->meta_data);
+ dir_value->meta_data = dirmeta;
+ }
+ else
+ {
+ dir_value = g_new0 (ParsedDirectoryData, 1);
+ dir_value->tree_data = parsed_tree_data_new ();
+ dir_value->metadata_sha256 = g_strdup (g_checksum_get_string (dir_meta_checksum));
+ dir_value->meta_data = dirmeta;
+ g_hash_table_insert (tree->directories, g_strdup (basename), dir_value);
+ }
+
+ ret = TRUE;
+ *dir = dir_value;
+ out:
+ if (dir_meta_checksum)
+ g_checksum_free (dir_meta_checksum);
+ return ret;
+}
+
+static gboolean
+add_one_file_to_tree_and_import (OstreeRepo *self,
+ const char *basename,
+ const char *abspath,
+ ParsedTreeData *tree,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GChecksum *checksum = NULL;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_assert (tree != NULL);
+
+ if (!link_one_file (self, abspath, OSTREE_OBJECT_TYPE_FILE,
+ TRUE, FALSE, &checksum, error))
+ goto out;
+
+ g_hash_table_replace (tree->files, g_strdup (basename),
+ g_strdup (g_checksum_get_string (checksum)));
+
+ ret = TRUE;
+ out:
+ if (checksum)
+ g_checksum_free (checksum);
+ return ret;
+}
+
+static gboolean
+add_one_path_to_tree_and_import (OstreeRepo *self,
+ const char *base,
+ const char *filename,
+ ParsedTreeData *tree,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GPtrArray *components = NULL;
+ struct stat stbuf;
+ char *component_abspath = NULL;
+ ParsedTreeData *current_tree = tree;
+ const char *component = NULL;
+ const char *file_sha1;
+ char *abspath = NULL;
+ ParsedDirectoryData *dir;
+ int i;
+ gboolean is_directory;
+
+ if (!check_path (filename, error))
+ goto out;
+
+ abspath = g_build_filename (base, filename, NULL);
+
+ if (lstat (abspath, &stbuf) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ is_directory = S_ISDIR(stbuf.st_mode);
+
+ if (components)
+ g_ptr_array_free (components, TRUE);
+ components = ot_util_path_split (filename);
+ g_assert (components->len > 0);
+
+ current_tree = tree;
+ for (i = 0; i < components->len; i++)
+ {
+ component = components->pdata[i];
+ g_free (component_abspath);
+ component_abspath = ot_util_path_join_n (base, components, i);
+ file_sha1 = g_hash_table_lookup (current_tree->files, component);
+ dir = g_hash_table_lookup (current_tree->directories, component);
+
+ g_assert_cmpstr (component, !=, ".");
+
+ if (i < components->len - 1)
+ {
+ if (file_sha1 != NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Encountered non-directory '%s' in '%s'",
+ component,
+ filename);
+ goto out;
+ }
+ /* Implicitly add intermediate directories */
+ if (!add_one_directory_to_tree_and_import (self, component,
+ component_abspath, current_tree, &dir,
+ error))
+ goto out;
+ g_assert (dir != NULL);
+ current_tree = dir->tree_data;
+ }
+ else if (is_directory)
+ {
+ if (file_sha1 != NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "File '%s' can't be overwritten by directory",
+ filename);
+ goto out;
+ }
+ if (!add_one_directory_to_tree_and_import (self, component,
+ abspath, current_tree, &dir,
+ error))
+ goto out;
+ }
+ else
+ {
+ g_assert (!is_directory);
+ if (dir != NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "File '%s' can't be overwritten by directory",
+ filename);
+ goto out;
+ }
+ if (!add_one_file_to_tree_and_import (self, component, abspath,
+ current_tree, error))
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+ out:
+ g_free (component_abspath);
+ g_free (abspath);
+ return ret;
+}
+
+static gboolean
+add_files_to_tree_and_import (OstreeRepo *self,
+ const char *base,
+ GPtrArray *added_files,
+ ParsedTreeData *tree,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int i;
+
+ for (i = 0; i < added_files->len; i++)
+ {
+ const char *path = added_files->pdata[i];
+
+ if (!add_one_path_to_tree_and_import (self, base, path, tree, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+commit_parsed_tree (OstreeRepo *self,
+ const char *subject,
+ const char *body,
+ GVariant *metadata,
+ ParsedDirectoryData *root,
+ GChecksum **out_commit,
+ GError **error)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+ gboolean ret = FALSE;
+ GChecksum *root_checksum = NULL;
+ GChecksum *ret_commit = NULL;
+ GVariant *commit = NULL;
+ GDateTime *now = NULL;
+
+ if (!import_parsed_tree (self, root->tree_data, &root_checksum, error))
+ goto out;
+
+ now = g_date_time_new_now_utc ();
+ commit = g_variant_new ("(u@a{sv}ssstss)",
+ OSTREE_COMMIT_VERSION,
+ create_empty_gvariant_dict (),
+ priv->current_head ? priv->current_head : "",
+ subject, body ? body : "",
+ g_date_time_to_unix (now) / G_TIME_SPAN_SECOND,
+ g_checksum_get_string (root_checksum),
+ root->metadata_sha256);
+ if (!import_gvariant_object (self, OSTREE_SERIALIZED_COMMIT_VARIANT,
+ commit, &ret_commit, error))
+ goto out;
+
+ if (!write_checksum_file (priv->head_ref_path, g_checksum_get_string (ret_commit), error))
+ goto out;
+
+ g_free (priv->current_head);
+ priv->current_head = g_strdup (g_checksum_get_string (ret_commit));
+
+ ret = TRUE;
+ *out_commit = ret_commit;
+ out:
+ if (root_checksum)
+ g_checksum_free (root_checksum);
+ if (commit)
+ g_variant_unref (commit);
+ if (now)
+ g_date_time_unref (now);
+ return ret;
+}
+
+static gboolean
+import_root (OstreeRepo *self,
+ const char *base,
+ ParsedDirectoryData **out_root,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ ParsedDirectoryData *ret_root = NULL;
+ GVariant *root_metadata = NULL;
+ GChecksum *root_meta_checksum = NULL;
+
+ if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
+ goto out;
+
+ ret_root = g_new0 (ParsedDirectoryData, 1);
+ ret_root->tree_data = parsed_tree_data_new ();
+ ret_root->meta_data = root_metadata;
+ root_metadata = NULL;
+ ret_root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
+
+ ret = TRUE;
+ *out_root = ret_root;
+ ret_root = NULL;
+ out:
+ if (root_metadata)
+ g_variant_unref (root_metadata);
+ if (root_meta_checksum)
+ g_checksum_free (root_meta_checksum);
+ parsed_directory_data_free (ret_root);
+ return ret;
+}
+
+gboolean
+ostree_repo_commit (OstreeRepo *self,
+ const char *subject,
+ const char *body,
+ GVariant *metadata,
+ const char *base,
+ GPtrArray *modified_files,
+ GPtrArray *removed_files,
+ GChecksum **out_commit,
+ GError **error)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+ gboolean ret = FALSE;
+ ParsedDirectoryData *root = NULL;
+ GVariant *previous_commit = NULL;
+ GChecksum *ret_commit_checksum = NULL;
+ char *orig_root_metadata_sha256 = NULL;
+ GVariant *root_metadata = NULL;
+ GChecksum *root_meta_checksum = NULL;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (priv->inited, FALSE);
+
+ if (priv->current_head)
+ {
+ if (!load_commit_and_trees (self, priv->current_head, &previous_commit, &root, error))
+ goto out;
+ if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
+ goto out;
+ g_variant_unref (root->meta_data);
+ root->meta_data = root_metadata;
+ root_metadata = NULL;
+ root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
+ }
+ else
+ {
+ /* Initial commit */
+ if (!import_root (self, base, &root, error))
+ goto out;
+ }
+
+ if (!remove_files_from_tree (self, base, removed_files, root->tree_data, error))
+ goto out;
+
+ if (!add_files_to_tree_and_import (self, base, modified_files, root->tree_data, error))
+ goto out;
+
+ if (!commit_parsed_tree (self, subject, body, metadata, root,
+ &ret_commit_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ if (ret_commit_checksum)
+ g_checksum_free (ret_commit_checksum);
+ }
+ else
+ {
+ *out_commit = ret_commit_checksum;
+ }
+ if (previous_commit)
+ g_variant_unref (previous_commit);
+ parsed_directory_data_free (root);
+ if (root_metadata)
+ g_variant_unref (root_metadata);
+ if (root_meta_checksum)
+ g_checksum_free (root_meta_checksum);
+ return ret;
+}
+
+gboolean
+ostree_repo_commit_from_filelist_fd (OstreeRepo *self,
+ const char *subject,
+ const char *body,
+ GVariant *metadata,
+ const char *base,
+ int fd,
+ char separator,
+ GChecksum **out_commit,
+ GError **error)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+ gboolean ret = FALSE;
+ ParsedDirectoryData *root = NULL;
+ GVariant *previous_commit = NULL;
+ GChecksum *ret_commit_checksum = NULL;
+ GUnixInputStream *in = NULL;
+ GDataInputStream *datain = NULL;
+ char *filename = NULL;
+ gsize filename_len;
+ GError *temp_error = NULL;
+ GVariant *root_metadata = NULL;
+ GChecksum *root_meta_checksum = NULL;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (priv->inited, FALSE);
+
+ /* We're overwriting the tree */
+ if (!import_root (self, base, &root, error))
+ goto out;
+
+ in = (GUnixInputStream*)g_unix_input_stream_new (fd, FALSE);
+ datain = g_data_input_stream_new ((GInputStream*)in);
+
+ while ((filename = g_data_input_stream_read_upto (datain, &separator, 1,
+ &filename_len, NULL, &temp_error)) != NULL)
+ {
+ if (!g_data_input_stream_read_byte (datain, NULL, &temp_error))
+ {
+ if (temp_error != NULL)
+ {
+ g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
+ goto out;
+ }
+ }
+ if (!add_one_path_to_tree_and_import (self, base, filename, root->tree_data, error))
+ goto out;
+ g_free (filename);
+ filename = NULL;
+ }
+ if (filename == NULL && temp_error != NULL)
+ {
+ g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
+ goto out;
+ }
+ if (!commit_parsed_tree (self, subject, body, metadata,
+ root, &ret_commit_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ if (ret_commit_checksum)
+ g_checksum_free (ret_commit_checksum);
+ }
+ else
+ {
+ *out_commit = ret_commit_checksum;
+ }
+ if (root_metadata)
+ g_variant_unref (root_metadata);
+ if (root_meta_checksum)
+ g_checksum_free (root_meta_checksum);
+ g_clear_object (&datain);
+ g_clear_object (&in);
+ g_free (filename);
+ parsed_directory_data_free (root);
+ return ret;
+
+}
+
+static gboolean
+iter_object_dir (OstreeRepo *self,
+ GFile *dir,
+ OstreeRepoObjectIter callback,
+ gpointer user_data,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GError *temp_error = NULL;
+ GFileEnumerator *enumerator = NULL;
+ GFileInfo *file_info = NULL;
+ char *dirpath = NULL;
+
+ dirpath = g_file_get_path (dir);
+
+ enumerator = g_file_enumerate_children (dir, "standard::name,standard::type,unix::*",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL,
+ error);
+ if (!enumerator)
+ goto out;
+
+ while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
+ {
+ const char *name;
+ guint32 type;
+ name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
+ type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+
+ if (type != G_FILE_TYPE_DIRECTORY
+ && (g_str_has_suffix (name, ".meta")
+ || g_str_has_suffix (name, ".file")))
+ {
+ char *dot;
+ char *path;
+
+ dot = strrchr (name, '.');
+ g_assert (dot);
+
+ if ((dot - name) == 62)
+ {
+ path = g_build_filename (dirpath, name, NULL);
+ callback (self, path, file_info, user_data);
+ g_free (path);
+ }
+ }
+
+ g_object_unref (file_info);
+ }
+ if (file_info == NULL && temp_error != NULL)
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ if (!g_file_enumerator_close (enumerator, NULL, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ g_free (dirpath);
+ return ret;
+}
+
+gboolean
+ostree_repo_iter_objects (OstreeRepo *self,
+ OstreeRepoObjectIter callback,
+ gpointer user_data,
+ GError **error)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+ GFile *objectdir = NULL;
+ GFileEnumerator *enumerator = NULL;
+ gboolean ret = FALSE;
+ GFileInfo *file_info = NULL;
+ GError *temp_error = NULL;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (priv->inited, FALSE);
+
+ objectdir = ot_util_new_file_for_path (priv->objects_path);
+ enumerator = g_file_enumerate_children (objectdir, "standard::name,standard::type,unix::*",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL,
+ error);
+ if (!enumerator)
+ goto out;
+
+ while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
+ {
+ const char *name;
+ guint32 type;
+
+ name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
+ type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+
+ if (strlen (name) == 2 && type == G_FILE_TYPE_DIRECTORY)
+ {
+ GFile *objdir = g_file_get_child (objectdir, name);
+ if (!iter_object_dir (self, objdir, callback, user_data, error))
+ {
+ g_object_unref (objdir);
+ goto out;
+ }
+ g_object_unref (objdir);
+ }
+ g_object_unref (file_info);
+ }
+ if (file_info == NULL && temp_error != NULL)
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ if (!g_file_enumerator_close (enumerator, NULL, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ g_clear_object (&file_info);
+ g_clear_object (&enumerator);
+ g_clear_object (&objectdir);
+ return ret;
+}
+
+gboolean
+ostree_repo_load_variant (OstreeRepo *repo,
+ const char *sha256,
+ OstreeSerializedVariantType *out_type,
+ GVariant **out_variant,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ OstreeSerializedVariantType ret_type;
+ GVariant *ret_variant = NULL;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (!load_gvariant_object_unknown (repo, sha256, &ret_type, &ret_variant, error))
+ goto out;
+
+ ret = TRUE;
+ *out_type = ret_type;
+ *out_variant = ret_variant;
+ out:
+ if (!ret)
+ {
+ if (ret_variant)
+ g_variant_unref (ret_variant);
+ g_prefix_error (error, "Failed to load metadata variant '%s': ", sha256);
+ }
+ return ret;
+}
+
+const char *
+ostree_repo_get_head (OstreeRepo *self)
+{
+ OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+ g_return_val_if_fail (priv->inited, NULL);
+
+ return priv->current_head;
+}
+
+static gboolean
+resolve_ref (OstreeRepo *self,
+ const char *ref,
+ char **resolved,
+ GError **error)
+{
+ if (strcmp (ref, "HEAD") == 0)
+ {
+ *resolved = g_strdup (ostree_repo_get_head (self));
+ return TRUE;
+ }
+ else if (strlen (ref) == 64)
+ {
+ *resolved = g_strdup (ref);
+ return TRUE;
+ }
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid ref '%s' (must be SHA256 or HEAD)", ref);
+ return FALSE;
+}
+
+static gboolean
+checkout_tree (OstreeRepo *self,
+ ParsedTreeData *tree,
+ const char *destination,
+ GError **error);
+
+static gboolean
+checkout_one_directory (OstreeRepo *self,
+ const char *destination,
+ const char *dirname,
+ ParsedDirectoryData *dir,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ char *dest_path = NULL;
+ guint32 version, uid, gid, mode;
+ GVariant *xattr_variant = NULL;
+ const guint8 *xattrs = NULL;
+ gsize xattr_len;
+
+ dest_path = g_build_filename (destination, dirname, NULL);
+
+ g_variant_get (dir->meta_data, "(uuuu@a(ayay))",
+ &version, &uid, &gid, &mode,
+ &xattr_variant);
+
+ if (mkdir (dest_path, (mode_t)mode) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ g_prefix_error (error, "Failed to create directory '%s': ", dest_path);
+ goto out;
+ }
+
+ if (!checkout_tree (self, dir->tree_data, dest_path, error))
+ goto out;
+
+ /* TODO - xattrs */
+
+ ret = TRUE;
+ out:
+ g_free (dest_path);
+ g_variant_unref (xattr_variant);
+ return ret;
+}
+
+static gboolean
+checkout_tree (OstreeRepo *self,
+ ParsedTreeData *tree,
+ const char *destination,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GHashTableIter hash_iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&hash_iter, tree->files);
+ while (g_hash_table_iter_next (&hash_iter, &key, &value))
+ {
+ const char *filename = key;
+ const char *checksum = value;
+ char *object_path;
+ char *dest_path;
+
+ object_path = get_object_path (self, checksum, OSTREE_OBJECT_TYPE_FILE);
+ dest_path = g_build_filename (destination, filename, NULL);
+ if (link (object_path, dest_path) < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ g_free (object_path);
+ g_free (dest_path);
+ goto out;
+ }
+ g_free (object_path);
+ g_free (dest_path);
+ }
+
+ g_hash_table_iter_init (&hash_iter, tree->directories);
+ while (g_hash_table_iter_next (&hash_iter, &key, &value))
+ {
+ const char *dirname = key;
+ ParsedDirectoryData *dir = value;
+
+ if (!checkout_one_directory (self, destination, dirname, dir, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+gboolean
+ostree_repo_checkout (OstreeRepo *self,
+ const char *ref,
+ const char *destination,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GVariant *commit = NULL;
+ char *resolved = NULL;
+ char *root_meta_sha = NULL;
+ ParsedDirectoryData *root = NULL;
+
+ if (g_file_test (destination, G_FILE_TEST_EXISTS))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Destination path '%s' already exists",
+ destination);
+ goto out;
+ }
+
+ if (!resolve_ref (self, ref, &resolved, error))
+ goto out;
+
+ if (!load_commit_and_trees (self, resolved, &commit, &root, error))
+ goto out;
+
+ if (!checkout_one_directory (self, destination, NULL, root, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ g_free (resolved);
+ if (commit)
+ g_variant_unref (commit);
+ parsed_directory_data_free (root);
+ g_free (root_meta_sha);
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+/* ostree-repo.h */
+
+#ifndef _OSTREE_REPO
+#define _OSTREE_REPO
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_REPO ostree_repo_get_type()
+#define OSTREE_REPO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), OSTREE_TYPE_REPO, OstreeRepo))
+#define OSTREE_REPO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), OSTREE_TYPE_REPO, OstreeRepoClass))
+#define OSTREE_IS_REPO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OSTREE_TYPE_REPO))
+#define OSTREE_IS_REPO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), OSTREE_TYPE_REPO))
+#define OSTREE_REPO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), OSTREE_TYPE_REPO, OstreeRepoClass))
+
+typedef struct {
+ GObject parent;
+} OstreeRepo;
+
+typedef struct {
+ GObjectClass parent_class;
+} OstreeRepoClass;
+
+GType ostree_repo_get_type (void);
+
+OstreeRepo* ostree_repo_new (const char *path);
+
+gboolean ostree_repo_check (OstreeRepo *self, GError **error);
+
+gboolean ostree_repo_link_file (OstreeRepo *self,
+ const char *path,
+ gboolean ignore_exists,
+ gboolean force,
+ GError **error);
+
+const char * ostree_repo_get_head (OstreeRepo *self);
+
+gboolean ostree_repo_load_variant (OstreeRepo *self,
+ const char *sha256,
+ OstreeSerializedVariantType *out_type,
+ GVariant **out_variant,
+ GError **error);
+
+gboolean ostree_repo_commit (OstreeRepo *self,
+ const char *subject,
+ const char *body,
+ GVariant *metadata,
+ const char *base,
+ GPtrArray *modified_files,
+ GPtrArray *removed_files,
+ GChecksum **out_commit,
+ GError **error);
+
+gboolean ostree_repo_commit_from_filelist_fd (OstreeRepo *self,
+ const char *subject,
+ const char *body,
+ GVariant *metadata,
+ const char *base,
+ int fd,
+ char separator,
+ GChecksum **out_commit,
+ GError **error);
+
+gboolean ostree_repo_checkout (OstreeRepo *self,
+ const char *ref,
+ const char *destination,
+ GError **error);
+
+typedef void (*OstreeRepoObjectIter) (OstreeRepo *self, const char *path,
+ GFileInfo *fileinfo, gpointer user_data);
+
+gboolean ostree_repo_iter_objects (OstreeRepo *self,
+ OstreeRepoObjectIter callback,
+ gpointer user_data,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* _OSTREE_REPO */
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_TYPES_H__
+#define __OSTREE_TYPES_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_REPO_DIR ".ot"
+
+G_END_DECLS
+
+#endif
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_H__
+
+#include <ostree-core.h>
+#include <ostree-repo.h>
+#include <ostree-types.h>
+
+#endif
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include <glib-unix.h>
+#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
+
+#include <string.h>
+
+#include "otutil.h"
+
+gboolean
+ot_util_ensure_directory (const char *path, gboolean with_parents, GError **error)
+{
+ GFile *dir;
+ GError *temp_error = NULL;
+ gboolean ret = FALSE;
+
+ dir = g_file_new_for_path (path);
+ if (with_parents)
+ ret = g_file_make_directory_with_parents (dir, NULL, &temp_error);
+ else
+ ret = g_file_make_directory (dir, NULL, &temp_error);
+ if (!ret)
+ {
+ if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ else
+ g_clear_error (&temp_error);
+ }
+
+ ret = TRUE;
+ out:
+ g_clear_object (&dir);
+ return ret;
+}
+
+
+char *
+ot_util_get_file_contents_utf8 (const char *path,
+ GError **error)
+{
+ char *contents;
+ gsize len;
+ if (!g_file_get_contents (path, &contents, &len, error))
+ return NULL;
+ if (!g_utf8_validate (contents, len, NULL))
+ {
+ g_free (contents);
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "File %s contains invalid UTF-8",
+ path);
+ return NULL;
+ }
+ return contents;
+}
+
+GInputStream *
+ot_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error)
+{
+ GInputStream *ret = NULL;
+ int fd;
+ int flags = O_RDONLY;
+ char *path = NULL;
+
+ path = g_file_get_path (file);
+#ifdef O_NOATIME
+ flags |= O_NOATIME;
+#endif
+ fd = open (path, flags);
+ if (fd < 0)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ ret = (GInputStream*)g_unix_input_stream_new (fd, TRUE);
+
+ out:
+ g_free (path);
+ return ret;
+}
+
+/* Like g_file_new_for_path, but only do local stuff, not GVFS */
+GFile *
+ot_util_new_file_for_path (const char *path)
+{
+ return g_vfs_get_file_for_path (g_vfs_get_local (), path);
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_GIO_UTILS_H__
+#define __OSTREE_GIO_UTILS_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+GFile *ot_util_new_file_for_path (const char *path);
+
+gboolean ot_util_ensure_directory (const char *path, gboolean with_parents, GError **error);
+
+char * ot_util_get_file_contents_utf8 (const char *path, GError **error);
+
+GInputStream *ot_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error);
+
+G_END_DECLS
+
+#endif
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-unix-utils.h"
+
+#include <glib-unix.h>
+#include <gio/gio.h>
+#include <gio/gunixoutputstream.h>
+
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+gboolean
+ot_util_spawn_pager (GOutputStream **out_stream,
+ GError **error)
+{
+ const char *pager;
+ char *argv[2];
+ int stdin_fd;
+ pid_t pid;
+ gboolean ret = FALSE;
+ GOutputStream *ret_stream = NULL;
+
+ if (!isatty (1))
+ {
+ ret_stream = (GOutputStream*)g_unix_output_stream_new (1, TRUE);
+ }
+ else
+ {
+ pager = g_getenv ("GIT_PAGER");
+ if (pager == NULL)
+ pager = "less";
+
+ argv[0] = (char*)pager;
+ argv[1] = NULL;
+
+ if (!g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
+ NULL, NULL, &pid, &stdin_fd, NULL, NULL, error))
+ {
+ g_prefix_error (error, "%s", "Failed to spawn pager: ");
+ goto out;
+ }
+
+ ret_stream = (GOutputStream*)g_unix_output_stream_new (stdin_fd, TRUE);
+ }
+
+ *out_stream = ret_stream;
+ ret_stream = NULL;
+ ret = TRUE;
+ out:
+ g_clear_object (&ret_stream);
+ return ret;
+}
+
+static int
+compare_filenames_by_component_length (const char *a,
+ const char *b)
+{
+ char *a_slash, *b_slash;
+
+ a_slash = strchr (a, '/');
+ b_slash = strchr (b, '/');
+ while (a_slash && b_slash)
+ {
+ a = a_slash + 1;
+ b = b_slash + 1;
+ a_slash = strchr (a, '/');
+ b_slash = strchr (b, '/');
+ }
+ if (a_slash)
+ return -1;
+ else if (b_slash)
+ return 1;
+ else
+ return 0;
+}
+
+GPtrArray *
+ot_util_sort_filenames_by_component_length (GPtrArray *files)
+{
+ GPtrArray *array = g_ptr_array_sized_new (files->len);
+ memcpy (array->pdata, files->pdata, sizeof (gpointer) * files->len);
+ g_ptr_array_sort (array, (GCompareFunc) compare_filenames_by_component_length);
+ return array;
+}
+
+int
+ot_util_count_filename_components (const char *path)
+{
+ int i = 0;
+
+ while (path)
+ {
+ i++;
+ path = strchr (path, '/');
+ if (path)
+ path++;
+ }
+ return i;
+}
+
+gboolean
+ot_util_filename_has_dotdot (const char *path)
+{
+ char *p;
+ char last;
+
+ if (strcmp (path, "..") == 0)
+ return TRUE;
+ if (g_str_has_prefix (path, "../"))
+ return TRUE;
+ p = strstr (path, "/..");
+ if (!p)
+ return FALSE;
+ last = *(p + 1);
+ return last == '\0' || last == '/';
+}
+
+GPtrArray *
+ot_util_path_split (const char *path)
+{
+ GPtrArray *ret = NULL;
+ const char *p;
+ const char *slash;
+ int i;
+
+ g_return_val_if_fail (path[0] != '/', NULL);
+
+ ret = g_ptr_array_new ();
+ g_ptr_array_set_free_func (ret, g_free);
+
+ p = path;
+ do {
+ slash = strchr (p, '/');
+ if (!slash)
+ {
+ g_ptr_array_add (ret, g_strdup (p));
+ p = NULL;
+ }
+ else
+ {
+ g_ptr_array_add (ret, g_strndup (p, slash - p));
+ p = slash + 1;
+ }
+ } while (p && *p);
+
+ /* Canonicalize by removing duplicate '.' */
+ for (i = ret->len-1; i >= 0; i--)
+ {
+ if (strcmp (ret->pdata[i], ".") == 0)
+ g_ptr_array_remove_index (ret, i);
+ }
+
+ return ret;
+}
+
+char *
+ot_util_path_join_n (const char *base, GPtrArray *components, int n)
+{
+ int max = MIN(n+1, components->len);
+ GPtrArray *subcomponents;
+ char *path;
+ int i;
+
+ subcomponents = g_ptr_array_new ();
+
+ if (base != NULL)
+ g_ptr_array_add (subcomponents, (char*)base);
+
+ for (i = 0; i < max; i++)
+ {
+ g_ptr_array_add (subcomponents, components->pdata[i]);
+ }
+ g_ptr_array_add (subcomponents, NULL);
+
+ path = g_build_filenamev ((char**)subcomponents->pdata);
+ g_ptr_array_free (subcomponents, TRUE);
+
+ return path;
+}
+
+void
+ot_util_set_error_from_errno (GError **error,
+ gint saved_errno)
+{
+ g_set_error_literal (error,
+ G_UNIX_ERROR,
+ 0,
+ g_strerror (saved_errno));
+ errno = saved_errno;
+}
+
+int
+ot_util_open_file_read (const char *path, GError **error)
+{
+ char *dirname = NULL;
+ char *basename = NULL;
+ DIR *dir = NULL;
+ int fd = -1;
+
+ dirname = g_path_get_dirname (path);
+ basename = g_path_get_basename (path);
+ dir = opendir (dirname);
+ if (dir == NULL)
+ {
+ ot_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ fd = ot_util_open_file_read_at (dirfd (dir), basename, error);
+
+ out:
+ g_free (basename);
+ g_free (dirname);
+ if (dir != NULL)
+ closedir (dir);
+ return fd;
+}
+
+int
+ot_util_open_file_read_at (int dirfd, const char *name, GError **error)
+{
+ int fd;
+ int flags = O_RDONLY;
+
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+#ifdef O_NOATIME
+ flags |= O_NOATIME;
+#endif
+ fd = openat (dirfd, name, flags);
+ if (fd < 0)
+ ot_util_set_error_from_errno (error, errno);
+ return fd;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_UNIX_UTILS_H__
+#define __OSTREE_UNIX_UTILS_H__
+
+#include <gio/gio.h>
+#include <glib-unix.h>
+
+/* I just put all this shit here. Sue me. */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+G_BEGIN_DECLS
+
+gboolean ot_util_spawn_pager (GOutputStream **out_stream, GError **error);
+
+gboolean ot_util_filename_has_dotdot (const char *path);
+
+GPtrArray *ot_util_sort_filenames_by_component_length (GPtrArray *files);
+
+GPtrArray* ot_util_path_split (const char *path);
+
+char *ot_util_path_join_n (const char *base, GPtrArray *components, int n);
+
+int ot_util_count_filename_components (const char *path);
+
+int ot_util_open_file_read (const char *path, GError **error);
+
+int ot_util_open_file_read_at (int dirfd, const char *name, GError **error);
+
+void ot_util_set_error_from_errno (GError **error, gint saved_errno);
+
+G_END_DECLS
+
+#endif
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_UTIL_H__
+
+#include <ot-unix-utils.h>
+#include <ot-gio-utils.h>
+
+#endif
#include <string.h>
-#include "ht-builtins.h"
-
-static HacktreeBuiltin builtins[] = {
- { "checkout", hacktree_builtin_checkout, 0 },
- { "init", hacktree_builtin_init, 0 },
- { "commit", hacktree_builtin_commit, 0 },
- { "link-file", hacktree_builtin_link_file, 0 },
- { "log", hacktree_builtin_log, 0 },
- { "fsck", hacktree_builtin_fsck, 0 },
- { "show", hacktree_builtin_show, 0 },
+#include "ot-builtins.h"
+
+static OstreeBuiltin builtins[] = {
+ { "checkout", ostree_builtin_checkout, 0 },
+ { "init", ostree_builtin_init, 0 },
+ { "commit", ostree_builtin_commit, 0 },
+ { "link-file", ostree_builtin_link_file, 0 },
+ { "log", ostree_builtin_log, 0 },
+ { "fsck", ostree_builtin_fsck, 0 },
+ { "show", ostree_builtin_show, 0 },
{ NULL }
};
static int
usage (char **argv, gboolean is_error)
{
- HacktreeBuiltin *builtin = builtins;
+ OstreeBuiltin *builtin = builtins;
void (*print_func) (const gchar *format, ...);
if (is_error)
main (int argc,
char **argv)
{
- HacktreeBuiltin *builtin;
+ OstreeBuiltin *builtin;
const char *cmd;
g_type_init ();
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+
+static GOptionEntry options[] = {
+ { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+ { NULL }
+};
+
+gboolean
+ostree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error)
+{
+ GOptionContext *context;
+ gboolean ret = FALSE;
+ OstreeRepo *repo = NULL;
+ int i;
+ const char *commit;
+ const char *destination;
+
+ context = g_option_context_new ("COMMIT DESTINATION - Check out a commit into a filesystem tree");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (repo_path == NULL)
+ repo_path = ".";
+
+ repo = ostree_repo_new (repo_path);
+ if (!ostree_repo_check (repo, error))
+ goto out;
+
+ if (argc < 3)
+ {
+ gchar *help = g_option_context_get_help (context, TRUE, NULL);
+ g_printerr ("%s\n", help);
+ g_free (help);
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "COMMIT and DESTINATION must be specified");
+ goto out;
+ }
+
+ commit = argv[1];
+ destination = argv[2];
+
+ if (!ostree_repo_checkout (repo, commit, destination, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ g_clear_object (&repo);
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static gboolean separator_null;
+static int from_fd = -1;
+static gboolean from_stdin;
+static char *from_file;
+static char *subject;
+static char *body;
+static char **additions;
+static char **removals;
+
+static GOptionEntry options[] = {
+ { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+ { "subject", 's', 0, G_OPTION_ARG_STRING, &subject, "One line subject", "subject" },
+ { "body", 'b', 0, G_OPTION_ARG_STRING, &body, "Full description", "body" },
+ { "from-fd", 0, 0, G_OPTION_ARG_INT, &from_fd, "Read new tree files from fd", "file descriptor" },
+ { "from-stdin", 0, 0, G_OPTION_ARG_NONE, &from_stdin, "Read new tree files from stdin", "file descriptor" },
+ { "from-file", 0, 0, G_OPTION_ARG_FILENAME, &from_file, "Read new tree files from another file", "path" },
+ { "separator-null", 0, 0, G_OPTION_ARG_NONE, &separator_null, "", "Use '\\0' as filename separator, as with find -print0" },
+ { "add", 'a', 0, G_OPTION_ARG_FILENAME_ARRAY, &additions, "Relative file path to add", "filename" },
+ { "remove", 'r', 0, G_OPTION_ARG_FILENAME_ARRAY, &removals, "Relative file path to remove", "filename" },
+ { NULL }
+};
+
+gboolean
+ostree_builtin_commit (int argc, char **argv, const char *prefix, GError **error)
+{
+ GOptionContext *context;
+ gboolean ret = FALSE;
+ OstreeRepo *repo = NULL;
+ gboolean using_filename_cmdline;
+ gboolean using_filedescriptors;
+ GPtrArray *additions_array = NULL;
+ GPtrArray *removals_array = NULL;
+ GChecksum *commit_checksum = NULL;
+ char **iter;
+
+ context = g_option_context_new ("- Commit a new revision");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (repo_path == NULL)
+ repo_path = ".";
+ if (prefix == NULL)
+ prefix = ".";
+
+ repo = ostree_repo_new (repo_path);
+ if (!ostree_repo_check (repo, error))
+ goto out;
+
+ using_filename_cmdline = (removals || additions);
+ using_filedescriptors = (from_file || from_fd >= 0 || from_stdin);
+
+ if (!(using_filename_cmdline || using_filedescriptors))
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No additions or removals specified");
+ goto out;
+ }
+ if (using_filename_cmdline && using_filedescriptors)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "File descriptors may not be combined with --add or --remove");
+ goto out;
+ }
+
+ if (!subject)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "A subject must be specified with --subject");
+ goto out;
+ }
+
+ if (using_filename_cmdline)
+ {
+ g_assert (removals || additions);
+ additions_array = g_ptr_array_new ();
+ removals_array = g_ptr_array_new ();
+
+ if (additions)
+ for (iter = additions; *iter; iter++)
+ g_ptr_array_add (additions_array, *iter);
+ if (removals)
+ for (iter = removals; *iter; iter++)
+ g_ptr_array_add (removals_array, *iter);
+
+ if (!ostree_repo_commit (repo, subject, body, NULL,
+ prefix, additions_array,
+ removals_array,
+ &commit_checksum,
+ error))
+ goto out;
+ }
+ else if (using_filedescriptors)
+ {
+ char separator = separator_null ? '\0' : '\n';
+ gboolean temp_fd = -1;
+
+ if (from_stdin)
+ from_fd = 0;
+ else if (from_file)
+ {
+ temp_fd = ot_util_open_file_read (from_file, error);
+ if (temp_fd < 0)
+ {
+ g_prefix_error (error, "Failed to open '%s': ", from_file);
+ goto out;
+ }
+ from_fd = temp_fd;
+ }
+ if (!ostree_repo_commit_from_filelist_fd (repo, subject, body, NULL,
+ prefix, from_fd, separator,
+ &commit_checksum, error))
+ {
+ if (temp_fd >= 0)
+ close (temp_fd);
+ goto out;
+ }
+ if (temp_fd >= 0)
+ close (temp_fd);
+ }
+
+ ret = TRUE;
+ g_print ("%s\n", g_checksum_get_string (commit_checksum));
+ out:
+ if (context)
+ g_option_context_free (context);
+ g_clear_object (&repo);
+ if (removals_array)
+ g_ptr_array_free (removals_array, TRUE);
+ if (additions_array)
+ g_ptr_array_free (additions_array, TRUE);
+ if (commit_checksum)
+ g_checksum_free (commit_checksum);
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static gboolean quiet;
+
+static GOptionEntry options[] = {
+ { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
+ { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Don't display informational messages", NULL },
+ { NULL }
+};
+
+typedef struct {
+ guint n_objects;
+} HtFsckData;
+
+static void
+object_iter_callback (OstreeRepo *repo,
+ const char *path,
+ GFileInfo *file_info,
+ gpointer user_data)
+{
+ HtFsckData *data = user_data;
+ struct stat stbuf;
+ GChecksum *checksum = NULL;
+ GError *error = NULL;
+ char *dirname = NULL;
+ char *checksum_prefix = NULL;
+ char *checksum_string = NULL;
+ char *filename_checksum = NULL;
+ char *dot;
+
+ dirname = g_path_get_dirname (path);
+ checksum_prefix = g_path_get_basename (dirname);
+
+ /* nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink");
+ if (nlinks < 2 && !quiet)
+ g_printerr ("note: floating object: %s\n", path); */
+
+ if (!ostree_stat_and_checksum_file (-1, path, &checksum, &stbuf, &error))
+ goto out;
+
+ filename_checksum = g_strdup (g_file_info_get_name (file_info));
+ dot = strrchr (filename_checksum, '.');
+ g_assert (dot != NULL);
+ *dot = '\0';
+
+ checksum_string = g_strconcat (checksum_prefix, filename_checksum, NULL);
+
+ if (strcmp (checksum_string, g_checksum_get_string (checksum)) != 0)
+ {
+ g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n",
+ path, g_checksum_get_string (checksum));
+ }
+
+ data->n_objects++;
+
+ out:
+ if (checksum != NULL)
+ g_checksum_free (checksum);
+ g_free (dirname);
+ g_free (checksum_prefix);
+ g_free (checksum_string);
+ g_free (filename_checksum);
+ if (error != NULL)
+ {
+ g_printerr ("%s\n", error->message);
+ g_clear_error (&error);
+ }
+}
+
+gboolean
+ostree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error)
+{
+ GOptionContext *context;
+ HtFsckData data;
+ gboolean ret = FALSE;
+ OstreeRepo *repo = NULL;
+ const char *head;
+
+ context = g_option_context_new ("- Check the repository for consistency");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (repo_path == NULL)
+ repo_path = ".";
+
+ data.n_objects = 0;
+
+ repo = ostree_repo_new (repo_path);
+ if (!ostree_repo_check (repo, error))
+ goto out;
+
+ if (!ostree_repo_iter_objects (repo, object_iter_callback, &data, error))
+ goto out;
+
+ head = ostree_repo_get_head (repo);
+ if (!head)
+ {
+ if (!quiet)
+ g_printerr ("No HEAD file\n");
+ }
+
+ if (!quiet)
+ g_printerr ("Total Objects: %u\n", data.n_objects);
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ g_clear_object (&repo);
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static GOptionEntry options[] = {
+ { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
+ { NULL }
+};
+
+gboolean
+ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
+{
+ GOptionContext *context = NULL;
+ gboolean ret = FALSE;
+ char *htdir_path = NULL;
+ char *objects_path = NULL;
+ GFile *htdir = NULL;
+ GFile *objects_dir = NULL;
+
+ context = g_option_context_new ("- Check the repository for consistency");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (repo_path == NULL)
+ repo_path = ".";
+
+ htdir_path = g_build_filename (repo_path, OSTREE_REPO_DIR, NULL);
+ htdir = ot_util_new_file_for_path (htdir_path);
+
+ if (!g_file_make_directory (htdir, NULL, error))
+ goto out;
+
+ objects_path = g_build_filename (htdir_path, "objects", NULL);
+ objects_dir = g_file_new_for_path (objects_path);
+ if (!g_file_make_directory (objects_dir, NULL, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ g_free (htdir_path);
+ g_clear_object (&htdir);
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static gboolean ignore_exists;
+static gboolean force;
+
+static GOptionEntry options[] = {
+ { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+ { "ignore-exists", 'n', 0, G_OPTION_ARG_NONE, &ignore_exists, "Don't error if file exists", NULL },
+ { "force", 'f', 0, G_OPTION_ARG_NONE, &force, "If object exists, relink file", NULL },
+ { NULL }
+};
+
+gboolean
+ostree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error)
+{
+ GOptionContext *context;
+ gboolean ret = FALSE;
+ OstreeRepo *repo = NULL;
+ int i;
+
+ context = g_option_context_new ("- Create a new hard link in the repository");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (repo_path == NULL)
+ repo_path = ".";
+
+ repo = ostree_repo_new (repo_path);
+ if (!ostree_repo_check (repo, error))
+ goto out;
+
+ for (i = 0; i < argc-1; i++)
+ {
+ if (!ostree_repo_link_file (repo, argv[i+1], ignore_exists, force, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ g_clear_object (&repo);
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+
+static GOptionEntry options[] = {
+ { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+ { NULL }
+};
+
+gboolean
+ostree_builtin_log (int argc, char **argv, const char *prefix, GError **error)
+{
+ GOptionContext *context;
+ gboolean ret = FALSE;
+ OstreeRepo *repo = NULL;
+ GOutputStream *pager = NULL;
+ GVariant *commit = NULL;
+ char *head;
+
+ context = g_option_context_new ("- Show revision log");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (repo_path == NULL)
+ repo_path = ".";
+ if (prefix == NULL)
+ prefix = ".";
+
+ repo = ostree_repo_new (repo_path);
+ if (!ostree_repo_check (repo, error))
+ goto out;
+
+ head = g_strdup (ostree_repo_get_head (repo));
+ if (!head)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No HEAD exists");
+ goto out;
+ }
+
+ if (!ot_util_spawn_pager (&pager, error))
+ goto out;
+
+ while (TRUE)
+ {
+ OstreeSerializedVariantType type;
+ char *formatted = NULL;
+ guint32 version;
+ const char *parent;
+ const char *subject;
+ const char *body;
+ guint64 timestamp;
+ const char *contents;
+ const char *root_metadata;
+ GDateTime *time_obj = NULL;
+ char *formatted_date = NULL;
+ const char *body_newline;
+ gsize bytes_written;
+ GVariant *commit_metadata = NULL;
+ char *formatted_metadata = NULL;
+
+ if (commit)
+ g_variant_unref (commit);
+ if (!ostree_repo_load_variant (repo, head, &type, &commit, error))
+ goto out;
+
+ /* Ignore commit metadata for now */
+ g_variant_get (commit, "(u@a{sv}&s&s&st&s&s)",
+ &version, &commit_metadata, &parent, &subject, &body,
+ ×tamp, &contents, &root_metadata);
+ time_obj = g_date_time_new_from_unix_utc (timestamp);
+ formatted_date = g_date_time_format (time_obj, "%a %b %d %H:%M:%S %Y %z");
+ g_date_time_unref (time_obj);
+ time_obj = NULL;
+
+ formatted_metadata = g_variant_print (commit_metadata, TRUE);
+ g_variant_unref (commit_metadata);
+ formatted = g_strdup_printf ("commit %s\nSubject: %s\nDate: %s\nMetadata: %s\n\n",
+ head, subject, formatted_date, formatted_metadata);
+ g_free (formatted_metadata);
+ g_free (formatted_date);
+ formatted_date = NULL;
+
+ if (!g_output_stream_write_all (pager, formatted, strlen (formatted), &bytes_written, NULL, error))
+ {
+ g_free (formatted);
+ goto out;
+ }
+ g_free (formatted);
+
+ body_newline = strchr (body, '\n');
+ do {
+ gsize len;
+ if (!g_output_stream_write_all (pager, " ", 4, &bytes_written, NULL, error))
+ goto out;
+ len = body_newline ? body_newline - body : strlen (body);
+ if (!g_output_stream_write_all (pager, body, len, &bytes_written, NULL, error))
+ goto out;
+ if (!g_output_stream_write_all (pager, "\n\n", 2, &bytes_written, NULL, error))
+ goto out;
+ body_newline = strchr (body, '\n');
+ if (!body_newline)
+ break;
+ else
+ body_newline += 1;
+ } while (*body_newline);
+
+ if (strcmp (parent, "") == 0)
+ break;
+ g_free (head);
+ head = g_strdup (parent);
+ }
+
+ if (!g_output_stream_close (pager, NULL, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ if (commit)
+ g_variant_unref (commit);
+ g_clear_object (&repo);
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+
+static GOptionEntry options[] = {
+ { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+ { NULL }
+};
+
+gboolean
+ostree_builtin_show (int argc, char **argv, const char *prefix, GError **error)
+{
+ GOptionContext *context;
+ gboolean ret = FALSE;
+ OstreeRepo *repo = NULL;
+ int i;
+ const char *target;
+ OstreeSerializedVariantType type;
+ GVariant *variant = NULL;
+ char *formatted_variant = NULL;
+
+ context = g_option_context_new ("- Output a metadata object");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (repo_path == NULL)
+ repo_path = ".";
+
+ repo = ostree_repo_new (repo_path);
+ if (!ostree_repo_check (repo, error))
+ goto out;
+
+ if (argc < 2)
+ {
+ target = ostree_repo_get_head (repo);
+ if (!target)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No arguments specified and no HEAD exists");
+ goto out;
+ }
+ }
+ else
+ target = argv[1];
+
+ if (!ostree_repo_load_variant (repo, target, &type, &variant, error))
+ goto out;
+
+ g_print ("Object: %s\nType: %d\n", target, type);
+ formatted_variant = g_variant_print (variant, TRUE);
+ g_print ("%s\n", formatted_variant);
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ g_clear_object (&repo);
+ if (variant)
+ g_variant_unref (variant);
+ g_free (formatted_variant);
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_BUILTINS__
+#define __OSTREE_BUILTINS__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ OSTREE_BUILTIN_FLAG_NONE = 0,
+} OstreeBuiltinFlags;
+
+typedef struct {
+ const char *name;
+ gboolean (*fn) (int argc, char **argv, const char *prefix, GError **error);
+ int flags; /* OstreeBuiltinFlags */
+} OstreeBuiltin;
+
+gboolean ostree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_commit (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_log (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_show (int argc, char **argv, const char *prefix, GError **error);
+
+G_END_DECLS
+
+#endif
TMPDIR=${TMPDIR:-/tmp}
export TMPDIR
-test_tmpdir=`mktemp -d "$TMPDIR/hacktree-tests.XXXXXXXXXX"`
+test_tmpdir=`mktemp -d "$TMPDIR/ostree-tests.XXXXXXXXXX"`
cd "$test_tmpdir"
touch "$test_tmpdir/.test$$"
echo second > secondfile
mkdir ../repo
- ht_repo="--repo=../repo"
- export ht_repo
- hacktree init $ht_repo
- hacktree commit $ht_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
- hacktree commit $ht_repo -s "Test Commit 2" -b "Commit body second" --add=secondfile
- hacktree fsck -q $ht_repo
+ ot_repo="--repo=../repo"
+ export ot_repo
+ ostree init $ot_repo
+ ostree commit $ot_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
+ ostree commit $ot_repo -s "Test Commit 2" -b "Commit body second" --add=secondfile
+ ostree fsck -q $ot_repo
}
setup_test_repository2 () {
cd ..
mkdir repo
cd repo
- ht_repo="--repo=`pwd`"
+ ot_repo="--repo=`pwd`"
cd ../files
- export ht_repo
- hacktree init $ht_repo
- hacktree commit $ht_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
- hacktree commit $ht_repo -s "Test Commit 2" -b "Commit body second" --add=baz/cow --add=baz/saucer --add=baz/deeper/ohyeah --add=baz/another/y
- hacktree fsck -q $ht_repo
+ export ot_repo
+ ostree init $ot_repo
+ ostree commit $ot_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
+ ostree commit $ot_repo -s "Test Commit 2" -b "Commit body second" --add=baz/cow --add=baz/saucer --add=baz/deeper/ohyeah --add=baz/another/y
+ ostree fsck -q $ot_repo
}
trap 'die' EXIT
mkdir repo
cd repo
-hacktree init
+ostree init
echo 'ok init'
-hacktree fsck -q
+ostree fsck -q
echo 'ok fsck'
-hacktree link-file $files/foo
+ostree link-file $files/foo
echo 'ok link'
-hacktree fsck -q
+ostree fsck -q
echo 'ok link-fsk'
-hacktree link-file $files/cow
-hacktree fsck -q
+ostree link-file $files/cow
+ostree fsck -q
echo 'ok link-fsk2'
echo hello > yy
mkdir ../repo
-hacktree init --repo=../repo
+ostree init --repo=../repo
echo 'ok init'
-hacktree fsck --repo=../repo -q
+ostree fsck --repo=../repo -q
echo 'ok fsck'
-hacktree link-file --repo=../repo yy
+ostree link-file --repo=../repo yy
echo 'ok link'
mkdir ../repo
repo="--repo=../repo"
-hacktree init $repo
+ostree init $repo
echo 'ok init'
-hacktree commit $repo -s "Test Commit" -b "Commit body" --add=yy
+ostree commit $repo -s "Test Commit" -b "Commit body" --add=yy
echo 'ok commit'
-hacktree fsck -q $repo
+ostree fsck -q $repo
echo 'ok fsck'
mkdir ../repo
repo="--repo=../repo"
-hacktree init $repo
+ostree init $repo
echo 'ok init'
-hacktree commit $repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
+ostree commit $repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
echo 'ok commit 1'
-hacktree commit $repo -s "Test Commit 2" -b "Commit body first" --add=secondfile
+ostree commit $repo -s "Test Commit 2" -b "Commit body first" --add=secondfile
echo 'ok commit 2'
-hacktree fsck -q $repo
+ostree fsck -q $repo
echo 'ok fsck'
setup_test_repository1
echo 'ok setup'
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout1-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout1-head
echo 'ok checkout cmd'
cd $test_tmpdir/checkout1-head
assert_has_file firstfile
setup_test_repository2
echo 'ok setup'
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout2-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout2-head
echo 'ok checkout cmd'
cd $test_tmpdir/checkout2-head
assert_has_file firstfile
echo '1..4'
setup_test_repository2
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout2-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout2-head
echo 'ok setup'
cd $test_tmpdir/checkout2-head
-hacktree commit -s delete $ht_repo -r firstfile
+ostree commit -s delete $ot_repo -r firstfile
echo 'ok rm firstfile'
assert_has_file firstfile # It should still exist in this checkout
cd $test_tmpdir
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout3-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout3-head
echo 'ok checkout 3'
cd $test_tmpdir/checkout3-head
assert_not_has_file firstfile
echo "1..2"
setup_test_repository2
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout2-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout2-head
cd $test_tmpdir/checkout2-head
mkdir -p a/nested/tree
echo one > a/nested/tree/1
echo anotherone > another/nested/tree/1
echo whee2 > another/whee
# FIXME - remove grep for .
-find | grep -v '^\.$' | hacktree commit $ht_repo --from-stdin -s "From find"
+find | grep -v '^\.$' | ostree commit $ot_repo --from-stdin -s "From find"
echo "ok commit stdin"
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout3-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout3-head
cd $test_tmpdir/checkout3-head
assert_has_file a/nested/2
assert_file_has_content a/nested/2 'two2'
. libtest.sh
-echo "1..2"
+echo "1..1"
setup_test_repository2
-hacktree log $ht_repo > $test_tmpdir/log.txt
+ostree log $ot_repo > $test_tmpdir/log.txt
assert_file_has_content $test_tmpdir/log.txt "Test Commit 1"
assert_file_has_content $test_tmpdir/log.txt "Test Commit 2"
echo "ok log"